单元化适配。未完成: 1.适配 reactive client; 2.unit context 适配 sct; 3. 单元化服务发现适配 ns type

pull/1681/head
shedfreewu 1 month ago
parent dd17356c3f
commit cd6747be8f

@ -22,3 +22,4 @@
- [feat: support config ratelimit addresses and remote task interval.](https://github.com/Tencent/spring-cloud-tencent/pull/1679)
- [docs:optimize tsf example.](https://github.com/Tencent/spring-cloud-tencent/pull/1710)
- [feat:support TSF certificate manager.](https://github.com/Tencent/spring-cloud-tencent/pull/1715)
- [feat:support tsf unit.](https://github.com/Tencent/spring-cloud-tencent/pull/1681)

@ -60,6 +60,11 @@
<artifactId>spring-cloud-starter-tencent-gateway-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-unit-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-auth</artifactId>

@ -53,7 +53,7 @@ public class PolarisReactiveDiscoveryClient implements ReactiveDiscoveryClient {
@Override
public Flux<ServiceInstance> getInstances(String serviceId) {
// TODO: shedfree 服务发现单元化
return Mono.justOrEmpty(serviceId).flatMapMany(loadInstancesFromPolaris())
.subscribeOn(Schedulers.boundedElastic());
}

@ -66,6 +66,10 @@ public final class MetadataConstant {
* TSF Metadata.
*/
public static final String TSF_METADATA = "TSF-Metadata";
/**
* TSF Unit.
*/
public static final String TSF_UNIT = "TSF-Unit";
/**
* Custom metadata.
*/

@ -42,6 +42,10 @@ public class OrderConstant {
* Order of encode router label interceptor.
*/
public static final int ROUTER_LABEL_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE;
/**
* Order of unit interceptor.
*/
public static final int UNIT_INTERCEPTOR_ORDER = 10;
}
/**
@ -57,6 +61,10 @@ public class OrderConstant {
* Order of encode router label interceptor.
*/
public static final int ROUTER_LABEL_INTERCEPTOR_ORDER = 0;
/**
* Order of unit interceptor.
*/
public static final int UNIT_INTERCEPTOR_ORDER = 10;
}
/**

@ -0,0 +1,32 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.common.util;
import com.tencent.polaris.api.utils.ClassUtils;
public final class EnvironmentUtils {
private final static boolean IS_GATEWAY = ClassUtils.isClassPresent("org.springframework.cloud.gateway.filter.GlobalFilter");
private EnvironmentUtils() {
}
public static boolean isGateway() {
return IS_GATEWAY;
}
}

@ -178,6 +178,12 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-unit-plugin</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-threadlocal-plugin</artifactId>

@ -18,6 +18,7 @@
<module>spring-cloud-starter-tencent-gateway-plugin</module>
<module>spring-cloud-starter-tencent-discovery-adapter-plugin</module>
<module>spring-cloud-tencent-lossless-plugin</module>
<module>spring-cloud-tencent-unit-plugin</module>
<module>spring-cloud-starter-tencent-threadlocal-plugin</module>
<module>spring-cloud-starter-tencent-trace-plugin</module>
<module>spring-cloud-starter-tencent-fault-tolerance</module>

@ -19,6 +19,11 @@
<artifactId>spring-cloud-starter-tencent-polaris-config</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-tencent-unit-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>

@ -19,6 +19,7 @@ package com.tencent.cloud.plugin.gateway.context;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@ -27,12 +28,15 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.MetadataContextUtils;
import com.tencent.cloud.plugin.unit.utils.SpringCloudUnitUtils;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
@ -47,6 +51,9 @@ import com.tencent.tsf.gateway.core.plugin.GatewayPluginFactory;
import com.tencent.tsf.gateway.core.plugin.IGatewayPlugin;
import com.tencent.tsf.gateway.core.util.CookieUtil;
import com.tencent.tsf.gateway.core.util.IdGenerator;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitTagPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
@ -94,6 +101,7 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
if (pathRewriteException != null) {
throw pathRewriteException;
}
// plugins will add metadata
if (exchange.getAttributes().containsKey(MetadataConstant.HeaderName.METADATA_CONTEXT)) {
MetadataContextHolder.set((MetadataContext) exchange.getAttributes().get(
@ -114,11 +122,17 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
ServerWebExchange apiRebuildExchange;
if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) {
apiRebuildExchange = msFilter(exchange, chain, groupContext, path);
if (TencentUnitManager.isEnable()) {
unitPreprocess(exchange);
apiRebuildExchange = unitFilter(exchange, chain, groupContext, path);
}
else {
apiRebuildExchange = externalFilter(exchange, chain, path);
if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) {
apiRebuildExchange = msFilter(exchange, chain, groupContext, path);
}
else {
apiRebuildExchange = externalFilter(exchange, chain, path);
}
}
ServerWebExchange pluginRebuildExchange = doPlugins(apiRebuildExchange,
@ -283,6 +297,83 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
return exchange.mutate().request(newRequest).build();
}
private void unitPreprocess(ServerWebExchange exchange) {
// 提前到这里清理
TencentUnitContext.removeAll();
ServerHttpRequest servletRequest = exchange.getRequest();
HttpHeaders headers = servletRequest.getHeaders();
String unitAttr = Optional.ofNullable(headers.getFirst(HeaderName.UNIT)).orElse("");
String cid = headers.getFirst(TencentUnitManager.getRouterIdentifierHeader());
for (String grayKey: TencentUnitManager.getGrayUnitHeaderKey()) {
TencentUnitContext.putGrayUserTag(UnitTagPosition.HEADER.name(), grayKey, headers.getFirst(grayKey));
}
Map<String, String> unitMap = JacksonUtils.deserialize2Map(URLDecoder.decode(unitAttr, StandardCharsets.UTF_8));
String path = exchange.getRequest().getURI().getPath();
if (CollectionUtils.isEmpty(unitMap)) {
SpringCloudUnitUtils.processFromCid(cid, path);
}
else {
SpringCloudUnitUtils.processFromUnitHeader(unitMap, path);
}
}
private ServerWebExchange unitFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext, PathContainer path) {
ServerHttpRequest request = exchange.getRequest();
String[] apis = rebuildUnitApi(request, path.value());
logger.debug("[unitFilter] path:{}, apis: {}", path, apis);
// check api
GroupContext.ContextRoute contextRoute = manager.getGroupUnitPathRoute(config.getGroup(), apis[0]);
if (contextRoute == null) {
String msg = String.format("[unitFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], path.value());
logger.warn(msg);
throw NotFoundException.create(true, msg);
}
updateRouteMetadata(exchange, contextRoute);
setTraceAttributes(contextRoute, GatewayConstant.UNIT_TYPE, GatewayConstant.UNIT_TRANSFER_TYPE);
exchange.getAttributes().put(GatewayConstant.CONTEXT_ROUTE, contextRoute);
MetadataContext metadataContext = exchange.getAttribute(
MetadataConstant.HeaderName.METADATA_CONTEXT);
if (metadataContext != null) {
// get unit ns
String routeNamespace = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_NAMESPACE_ID);
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, routeNamespace);
}
URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
// to correct path
ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
return exchange.mutate().request(newRequest).build();
}
/**
* e.g. "/context/system/svc/api/test" [ "GET|/svc/api/test", "/api/test"]
*/
String[] rebuildUnitApi(ServerHttpRequest request, String path) {
String[] pathSegments = path.split("/");
StringBuilder matchPath = new StringBuilder();
matchPath.append(request.getMethod().name()).append("|");
if (pathSegments.length > 3) {
matchPath.append("/").append(pathSegments[3]);
}
StringBuilder realPath = new StringBuilder();
int index = 4;
for (int i = index; i < pathSegments.length; i++) {
matchPath.append("/").append(pathSegments[i]);
realPath.append("/").append(pathSegments[i]);
}
if (path.endsWith("/")) {
matchPath.append("/");
realPath.append("/");
}
return new String[] {matchPath.toString(), realPath.toString()};
}
/**
* e.g. "/context/api/test" [ "GET|/api/test", "/api/test"]
*/

@ -58,6 +58,14 @@ public class ContextGatewayPropertiesManager {
* context -> {wildcard path key -> route}.
*/
private volatile ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> groupWildcardPathRouteMap = new ConcurrentHashMap<>();
/**
* context -> {unit path key -> route}.
*/
private volatile ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> groupUnitPathRouteMap = new ConcurrentHashMap<>();
/**
* context -> {unit wildcard path key -> route}.
*/
private volatile ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> groupUnitWildcardPathRouteMap = new ConcurrentHashMap<>();
/**
* group -> plugin info.
*/
@ -145,31 +153,47 @@ public class ContextGatewayPropertiesManager {
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupPathRouteMap = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupWildcardPathRouteMap = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupUnitPathRouteMap = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Map<String, GroupContext.ContextRoute>> newGroupUnitWildcardPathRouteMap = new ConcurrentHashMap<>();
if (groups != null) {
for (Map.Entry<String, GroupContext> entry : groups.entrySet()) {
GroupContext groupContext = entry.getValue();
Map<String, GroupContext.ContextRoute> newGroupPathRoute = new HashMap<>();
Map<String, GroupContext.ContextRoute> newGroupWildcardPathRoute = new HashMap<>();
Map<String, GroupContext.ContextRoute> newGroupUnitPathRoute = new HashMap<>();
Map<String, GroupContext.ContextRoute> newGroupUnitWildcardPathRoute = new HashMap<>();
for (GroupContext.ContextRoute route : groupContext.getRoutes()) {
String path = route.getPath();
// convert path parameter to group wildcard path
if (path.contains("{") && path.contains("}") || path.contains("*") || path.contains("?")) {
newGroupWildcardPathRoute.put(buildPathKey(groupContext, route), route);
newGroupUnitWildcardPathRoute.put(buildUnitPathKey(groupContext, route), route);
}
else {
newGroupPathRoute.put(buildPathKey(groupContext, route), route);
newGroupUnitPathRoute.put(buildUnitPathKey(groupContext, route), route);
if (StringUtils.isNotEmpty(route.getPathMapping())) {
newGroupPathRoute.put(buildPathMappingKey(groupContext, route), route);
newGroupUnitPathRoute.put(buildUnitPathKey(groupContext, route), route);
}
}
}
newGroupWildcardPathRouteMap.put(entry.getKey(), newGroupWildcardPathRoute);
newGroupPathRouteMap.put(entry.getKey(), newGroupPathRoute);
newGroupUnitWildcardPathRouteMap.put(entry.getKey(), newGroupUnitWildcardPathRoute);
newGroupUnitPathRouteMap.put(entry.getKey(), newGroupUnitPathRoute);
}
}
this.groupPathRouteMap = newGroupPathRouteMap;
this.groupWildcardPathRouteMap = newGroupWildcardPathRouteMap;
this.groupUnitPathRouteMap = newGroupUnitPathRouteMap;
this.groupUnitWildcardPathRouteMap = newGroupUnitWildcardPathRouteMap;
this.groups = groups;
}
@ -195,6 +219,24 @@ public class ContextGatewayPropertiesManager {
return null;
}
public GroupContext.ContextRoute getGroupUnitPathRoute(String group, String path) {
Map<String, GroupContext.ContextRoute> groupPathRouteMap = this.groupUnitPathRouteMap.get(group);
if (groupPathRouteMap != null && groupPathRouteMap.containsKey(path)) {
return groupPathRouteMap.get(path);
}
Map<String, GroupContext.ContextRoute> groupWildcardPathRouteMap = this.groupUnitWildcardPathRouteMap.get(group);
if (groupWildcardPathRouteMap != null) {
for (Map.Entry<String, GroupContext.ContextRoute> entry : groupWildcardPathRouteMap.entrySet()) {
boolean matched = antPathMatcher.match(entry.getKey(), path);
if (matched) {
return entry.getValue();
}
}
}
return null;
}
public void eagerLoad(PolarisDiscoveryClient polarisDiscoveryClient,
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
for (Map<String, GroupContext.ContextRoute> contextRouteMap : groupPathRouteMap.values()) {
@ -241,6 +283,10 @@ public class ContextGatewayPropertiesManager {
}
}
private String buildUnitPathKey(GroupContext groupContext, GroupContext.ContextRoute route) {
return String.format("%s|/%s%s", route.getMethod(), route.getService(), route.getPath());
}
private String buildPathMappingKey(GroupContext groupContext, GroupContext.ContextRoute route) {
return String.format("%s|%s", route.getMethod(), route.getPathMapping());
}

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tencent-plugin-starters</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-tencent-unit-plugin</artifactId>
<name>Spring Cloud Tencent Unit Plugin</name>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.config;
import com.tencent.cloud.common.tsf.ConditionalOnOnlyTsfConsulEnabled;
import com.tencent.tsf.unit.core.GatewayUnitArchCallback;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.TsfZoneFilterUnitCallback;
import com.tencent.tsf.unit.core.remote.TsfUnitConsulManager;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayUnitAutoConfiguration {
/**
* .
*/
@Configuration
@ConditionalOnOnlyTsfConsulEnabled
@ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter")
static class GatewayUnitEnable {
@Value("${spring.application.name:}")
private String applicationName;
@PostConstruct
public void init() {
TencentUnitManager.addArchCallback(new GatewayUnitArchCallback(applicationName));
TencentUnitManager.addRuleCallback(new TsfZoneFilterUnitCallback());
TsfUnitConsulManager.init();
}
}
}

@ -0,0 +1,124 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.tencent.cloud.common.tsf.ConditionalOnOnlyTsfConsulEnabled;
import com.tencent.cloud.plugin.unit.instrument.feign.UnitFeignRequestInterceptor;
import com.tencent.cloud.plugin.unit.instrument.resttemplate.UnitRestTemplateInterceptor;
import com.tencent.cloud.plugin.unit.plugin.UnitClientFinallyEnhancedPlugin;
import com.tencent.cloud.plugin.unit.plugin.UnitFeignEnhancedPlugin;
import com.tencent.cloud.plugin.unit.plugin.UnitRestTemplateEnhancedPlugin;
import com.tencent.cloud.plugin.unit.plugin.UnitServletPreEnhancedPlugin;
import com.tencent.tsf.unit.aspect.TsfUnitRouteAspect;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.TsfZoneFilterUnitCallback;
import com.tencent.tsf.unit.core.utils.TencentUnitUtils;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnOnlyTsfConsulEnabled
public class UnitAutoConfiguration {
@Bean
public UnitBeanPostProcessor unitPolarisDiscoveryClientBeanPostProcessor() {
return new UnitBeanPostProcessor();
}
@Bean
public UnitClientFinallyEnhancedPlugin unitClientFinallyEnhancedPlugin() {
return new UnitClientFinallyEnhancedPlugin();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
static class UnitServletFilterConfig {
@Bean
public UnitServletPreEnhancedPlugin unitServletPreEnhancedPlugin() {
return new UnitServletPreEnhancedPlugin();
}
}
/**
* .
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.cloud.gateway.filter.GlobalFilter")
static class MicroserviceUnitEnable {
static {
TencentUnitManager.addRuleCallback(new TsfZoneFilterUnitCallback());
TencentUnitUtils.enable();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
static class TsfUnitRestTemplateConfig {
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public UnitRestTemplateInterceptor tsfUnitRestTemplateInterceptor() {
return new UnitRestTemplateInterceptor();
}
@Bean
public SmartInitializingSingleton addTsfUnitRestTemplateInterceptorForRestTemplate(UnitRestTemplateInterceptor interceptor) {
return () -> restTemplates.forEach(restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
list.add(interceptor);
restTemplate.setInterceptors(list);
});
}
@Bean
public UnitRestTemplateEnhancedPlugin unitRestTemplateEnhancedPlugin() {
return new UnitRestTemplateEnhancedPlugin();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.Feign")
static class TsfUnitFeignConfig {
@Bean
@ConditionalOnMissingBean
public UnitFeignRequestInterceptor tsfUnitFeignRequestInterceptor() {
return new UnitFeignRequestInterceptor();
}
@Bean
public TsfUnitRouteAspect tsfUnitRouteAspect() {
return new TsfUnitRouteAspect();
}
@Bean
public UnitFeignEnhancedPlugin unitFeignEnhancedPlugin() {
return new UnitFeignEnhancedPlugin();
}
}
}

@ -0,0 +1,35 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.config;
import com.tencent.cloud.plugin.unit.discovery.UnitFeignEagerLoadSmartLifecycle;
import com.tencent.cloud.plugin.unit.discovery.UnitPolarisDiscoveryClient;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class UnitBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof PolarisDiscoveryClient discoveryClient) {
return new UnitPolarisDiscoveryClient(discoveryClient);
}
if (bean instanceof FeignEagerLoadSmartLifecycle) {
return new UnitFeignEagerLoadSmartLifecycle();
}
return bean;
}
}

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.discovery;
import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnitFeignEagerLoadSmartLifecycle extends FeignEagerLoadSmartLifecycle {
private static final Logger LOG = LoggerFactory.getLogger(UnitFeignEagerLoadSmartLifecycle.class);
public UnitFeignEagerLoadSmartLifecycle() {
super(null, null, null);
}
@Override
public void start() {
LOG.info("ignore feign eager load in unit mode");
}
}

@ -0,0 +1,74 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.discovery;
import java.util.List;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
public class UnitPolarisDiscoveryClient extends PolarisDiscoveryClient {
private static final Logger LOGGER = LoggerFactory.getLogger(UnitPolarisDiscoveryClient.class);
private final PolarisDiscoveryClient delegate;
public UnitPolarisDiscoveryClient(PolarisDiscoveryClient delegate) {
super(null);
this.delegate = delegate;
}
@Override
public String description() {
return delegate.description();
}
@Override
public List<ServiceInstance> getInstances(String service) {
if (TencentUnitManager.isEnable()) {
String[] parts = service.split("/");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[getInstance] service:{}, unit context:{}", service, TencentUnitContext.getCompositeContextMap());
}
if (parts.length != 2) {
String namespace = TencentUnitContext.getStringRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID);
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace);
return delegate.getInstances(service);
}
else {
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, parts[0]);
return delegate.getInstances(parts[1]);
}
}
else {
return delegate.getInstances(service);
}
}
@Override
public List<String> getServices() {
return delegate.getServices();
}
}

@ -0,0 +1,61 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.instrument.feign;
import java.net.URI;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.plugin.unit.utils.SpringCloudUnitUtils;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitArch;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.core.Ordered;
/**
* Interceptor used for setting Feign RequestTemplate metadata provider.
*
* @author lepdou, Hoatian Zhang
*/
public class UnitFeignRequestInterceptor implements RequestInterceptor, Ordered {
@Override
public int getOrder() {
return OrderConstant.Client.Feign.UNIT_INTERCEPTOR_ORDER;
}
@Override
public void apply(RequestTemplate requestTemplate) {
// 开启单元化
if (TencentUnitManager.isEnable()) {
String httpServiceName = requestTemplate.feignTarget().url();
URI uri = URI.create(httpServiceName);
// 截取http://provider-demo 为 provider-demo,
String serviceName = uri.getHost();
SpringCloudUnitUtils.preRequestRecordUnitContext(serviceName);
if (TencentUnitContext.containRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY)) {
UnitArch.Gateway gateway = (UnitArch.Gateway) TencentUnitContext.getObjectRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY);
requestTemplate.target(uri.getScheme() + "//" + gateway.getServiceName());
}
}
}
}

@ -0,0 +1,74 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.instrument.resttemplate;
import java.io.IOException;
import java.net.URI;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.plugin.unit.utils.SpringCloudUnitUtils;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitArch;
import org.springframework.core.Ordered;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Interceptor used for unit in RestTemplate.
*
* @author Shedfree Wu
*/
public class UnitRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered {
@Override
public int getOrder() {
return OrderConstant.Client.RestTemplate.UNIT_INTERCEPTOR_ORDER;
}
/**
* @see org.springframework.http.client.ClientHttpRequestInterceptor#intercept(org.springframework.http.HttpRequest, byte[], org.springframework.http.client.ClientHttpRequestExecution)
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
return execution.execute(getHttpRequestWrapper(request), body);
}
public HttpRequest getHttpRequestWrapper(HttpRequest httpRequest) {
// 未开启单元化, 直接返回
if (!TencentUnitManager.isEnable()) {
return httpRequest;
}
String serviceName = httpRequest.getURI().getHost();
SpringCloudUnitUtils.preRequestRecordUnitContext(serviceName);
// 不需要转发到网关, 直接返回
if (!TencentUnitContext.containRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY)) {
return httpRequest;
}
UnitArch.Gateway gateway = (UnitArch.Gateway) TencentUnitContext.getObjectRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY);
URI unitRouteUri = UriComponentsBuilder.fromUri(httpRequest.getURI()).host(gateway.getServiceName()).build()
.toUri();
return new UriModifyHttpRequestWrapper(httpRequest, unitRouteUri);
}
}

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.instrument.resttemplate;
import java.net.URI;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.support.HttpRequestWrapper;
public class UriModifyHttpRequestWrapper extends HttpRequestWrapper {
private final URI uri;
public UriModifyHttpRequestWrapper(HttpRequest request, URI uri) {
super(request);
this.uri = uri;
}
@Override
public URI getURI() {
return uri;
}
}

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.plugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.tsf.unit.core.TencentUnitContext;
import org.springframework.core.Ordered;
public class UnitClientFinallyEnhancedPlugin implements EnhancedPlugin {
public UnitClientFinallyEnhancedPlugin() {
}
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.FINALLY;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
TencentUnitContext.removeAll();
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

@ -0,0 +1,108 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.plugin;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.tsf.unit.core.TencentUnitContext;
import feign.Request;
import shade.polaris.com.google.common.collect.ImmutableMap;
import org.springframework.util.CollectionUtils;
/**
* Pre EnhancedPlugin for feign to encode unit metadata.
*
* @author Shedfree Wu
*/
public class UnitFeignEnhancedPlugin implements EnhancedPlugin {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.BEFORE_CALLING;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof Request request)) {
return;
}
TencentUnitContext.UnitCompositeContextMap unitCompositeContextMap = TencentUnitContext.getCompositeContextMap();
if (!com.tencent.polaris.api.utils.CollectionUtils.isEmpty(unitCompositeContextMap.getSystemContext())) {
buildMetadataHeader(request, unitCompositeContextMap.getSystemContext(), MetadataConstant.HeaderName.TSF_UNIT);
}
}
/**
* Set metadata into the request header for {@link Request} .
* @param request instance of {@link Request}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(Request request, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
/**
* Set headerMap into the request header for {@link Request} .
* @param request instance of {@link Request}
* @param headerMap header map .
*/
private void buildHeaderMap(Request request, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
Map<String, Collection<String>> headers = getModifiableHeaders(request);
headerMap.forEach((key, value) -> headers.put(key, Collections.singletonList(UrlUtils.encode(value))));
}
}
/**
* The value obtained directly from the headers method is an unmodifiable map.
* If the Feign client uses the URL, the original headers are unmodifiable.
* @param request feign request
* @return modifiable headers
*/
private Map<String, Collection<String>> getModifiableHeaders(Request request) {
Map<String, Collection<String>> headers;
headers = (Map<String, Collection<String>>) ReflectionUtils.getFieldValue(request, "headers");
if (!(headers instanceof LinkedHashMap)) {
headers = new LinkedHashMap<>(headers);
ReflectionUtils.setFieldValue(request, "headers", headers);
}
return headers;
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_UNIT_METADATA_PLUGIN_ORDER;
}
}

@ -0,0 +1,81 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.plugin;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.tsf.unit.core.TencentUnitContext;
import shade.polaris.com.google.common.collect.ImmutableMap;
import org.springframework.http.HttpRequest;
/**
* Pre EnhancedPlugin for rest template to encode unit metadata.
*
* @author Shedfree Wu
*/
public class UnitRestTemplateEnhancedPlugin implements EnhancedPlugin {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.BEFORE_CALLING;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof HttpRequest httpRequest)) {
return;
}
TencentUnitContext.UnitCompositeContextMap unitCompositeContextMap = TencentUnitContext.getCompositeContextMap();
if (!CollectionUtils.isEmpty(unitCompositeContextMap.getSystemContext())) {
buildMetadataHeader(httpRequest, unitCompositeContextMap.getSystemContext(), MetadataConstant.HeaderName.TSF_UNIT);
}
}
private void buildHeaderMap(HttpRequest request, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> request.getHeaders().set(key, UrlUtils.encode(value)));
}
}
/**
* Set metadata into the request header for {@link HttpRequest} .
*
* @param request instance of {@link HttpRequest}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(HttpRequest request, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_UNIT_METADATA_PLUGIN_ORDER;
}
}

@ -0,0 +1,86 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.plugin;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.tsf.unit.core.TencentUnitContext;
import shade.polaris.com.google.common.collect.ImmutableMap;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* Pre EnhancedPlugin for scg to encode unit metadata.
*
* @author Shedfree Wu
*/
public class UnitScgEnhancedPlugin implements EnhancedPlugin {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof ServerWebExchange exchange)) {
return;
}
// get request builder
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
TencentUnitContext.UnitCompositeContextMap unitCompositeContextMap = TencentUnitContext.getCompositeContextMap();
if (!com.tencent.polaris.api.utils.CollectionUtils.isEmpty(unitCompositeContextMap.getSystemContext())) {
buildMetadataHeader(builder, unitCompositeContextMap.getSystemContext(), MetadataConstant.HeaderName.TSF_UNIT);
}
context.setOriginRequest(exchange.mutate().request(builder.build()).build());
}
private void buildHeaderMap(ServerHttpRequest.Builder builder, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> builder.header(key, UrlUtils.encode(value)));
}
}
/**
* Set metadata into the request header for {@link ServerHttpRequest.Builder} .
* @param builder instance of {@link ServerHttpRequest.Builder}
* @param metadata metadata map .
* @param headerName target metadata http header name .
*/
private void buildMetadataHeader(ServerHttpRequest.Builder builder, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
buildHeaderMap(builder, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ClientPluginOrder.CONSUMER_UNIT_METADATA_PLUGIN_ORDER;
}
}

@ -0,0 +1,96 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.plugin.unit.plugin;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitTagPosition;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnitServletPreEnhancedPlugin implements EnhancedPlugin {
private static final Logger logger = LoggerFactory.getLogger(UnitServletPreEnhancedPlugin.class);
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Server.PRE;
}
@Override
public void run(EnhancedPluginContext context) throws Throwable {
if (!(context.getOriginRequest() instanceof HttpServletRequest httpServletRequest)) {
return;
}
if (!TencentUnitManager.isEnable()) {
return;
}
String unitContextEncoded = httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_UNIT);
if (StringUtils.isNotEmpty(unitContextEncoded)) {
Map<String, String> unitMap = JacksonUtils.deserialize2Map(URLDecoder.decode(unitContextEncoded, StandardCharsets.UTF_8));
TencentUnitContext.putSourceTags(unitMap);
if (TencentUnitManager.isContextPassingThroughEnabled()) {
// 不能传递所有的 key, 只需要传递客户要素和业务系统,灰度信息
Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER)).
ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER, value));
Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM)).
ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, value));
Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO)).
ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO, value));
}
else {
// 没有 passing through 标志,灰度信息也要透传
Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO)).
ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO, value));
}
if (logger.isDebugEnabled()) {
logger.debug("[getSerializeTagsFromRequestMeta] unit context:{}",
TencentUnitContext.getCompositeContextMap());
}
for (String grayKey : TencentUnitManager.getGrayUnitHeaderKey()) {
String value = httpServletRequest.getHeader(grayKey);
// 非空 value 才设置,便于匹配灰度规则时能否直接跳过
if (StringUtils.isNotEmpty(value)) {
TencentUnitContext.putGrayUserTag(UnitTagPosition.HEADER.name(), grayKey, value);
}
}
}
}
@Override
public int getOrder() {
return PluginOrderConstant.ServerPluginOrder.TRACE_SERVER_PRE_PLUGIN_ORDER;
}
}

@ -0,0 +1,529 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.cloud.plugin.unit.utils;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.EnvironmentUtils;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.UnitTagEngine;
import com.tencent.tsf.unit.core.exception.ErrorCode;
import com.tencent.tsf.unit.core.exception.TencentUnitException;
import com.tencent.tsf.unit.core.model.RoutingUnit;
import com.tencent.tsf.unit.core.model.UnitArch.Gateway;
import com.tencent.tsf.unit.core.model.UnitNamespace;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayMatchRoute;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayMatchRouteUnit;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRoute;
import com.tencent.tsf.unit.core.utils.TencentUnitUtils;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.ServiceInstance;
public final class SpringCloudUnitUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringCloudUnitUtils.class);
private static PolarisDiscoveryClient discoveryClient;
private SpringCloudUnitUtils() {
}
/**
* , feign/rest template , , , TencentUnitContext.
*/
public static void preRequestRecordUnitContext(String serviceName) {
if (discoveryClient == null) {
discoveryClient = ApplicationContextAwareUtils.getBean(PolarisDiscoveryClient.class);
}
TencentUnitContext.putSystemTagsFromUser();
String system = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM);
// 无论 gdu 还是 sdu, 都需要 system 信息,如果没有 system返回 false则直接返回
if (!checkSystem(system, serviceName)) {
return;
}
// 先进行灰度判断
RoutingUnit routingUnit = preRequestRecordGrayUnitContext();
String customerIdentifier = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER);
// 如果 dest unit id 存在,则直接设置 unit context 并返回
if (preRequestRecordIfDestUnitExist(system, customerIdentifier, serviceName)) {
return;
}
// 发生 gdu 容灾切换gdu 不在同一个 cloud 的,直接设置 unit context 并返回
if (preRequestRecordIfGduNotInLocalCloud(serviceName)) {
return;
}
boolean toSdu = true;
String gduUnitId = getGduUnitId();
UnitNamespace gduNs = TencentUnitManager.getUnitNamespaceId(gduUnitId, system);
boolean gduNsNotExist = (gduNs == null || StringUtils.isEmpty(gduNs.getId()));
// preRequestRecordIfDestUnitExist 可能需要 gdu 的信息,所以需要这里进行解析 gdu 和设置
if (gduNsNotExist) {
if (StringUtils.isEmpty(customerIdentifier)) {
String msg = String.format("[preRequestRecordUnitContext] gdu system not exist:%s, and customerIdentifier is empty, serviceName:%s", system, serviceName);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
// 没有 gdu 时,肯定是 sdu forward
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_SDU_FORWARD_ONLY, Boolean.TRUE.toString());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.FALSE.toString());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] gduNsNotExist. only forward to sdu, system:{}, service name:{}, unit context:{}",
system, serviceName, TencentUnitContext.getCompositeContextMap());
}
}
else {
// 有 gdu 对应 ns
// 没有客户要素,限定只转发到 gdu
if (StringUtils.isEmpty(customerIdentifier)) {
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_SDU_FORWARD_ONLY, Boolean.FALSE.toString());
// 前面校验了,这里直接 set context
setUnitContext(gduUnitId, gduNs, serviceName);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] cid or business system is empty. only forward to gdu, service name:{}, unit context:{}",
serviceName, TencentUnitContext.getCompositeContextMap());
}
return;
}
String gduServiceId = getGduServiceId(serviceName, gduNs.getId());
List<ServiceInstance> gduServices = discoveryClient.getInstances(gduServiceId);
// gdu 有实例,直接转入 gdu不需要计算客户号
if (CollectionUtils.isNotEmpty(gduServices)) {
toSdu = false;
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_INSTANCE_EXIST, Boolean.TRUE.toString());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_SDU_FORWARD_ONLY, Boolean.FALSE.toString());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE, serviceName);
setUnitContext(gduUnitId, gduNs, serviceName);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] gdu first and exist instance. forward to gdu, service name:{}, unit context:{}",
serviceName, TencentUnitContext.getCompositeContextMap());
}
}
}
if (toSdu) {
String graySduUnitId = TencentUnitContext.getGraySystemTag(TencentUnitContext.GRAY_CLOUD_SPACE_SDU_UNIT_ID);
if (StringUtils.isNotEmpty(graySduUnitId)) {
// gray sdu, 直接获取 unit id, 不需要解析客户要素
checkUnitIdAndSetContext(graySduUnitId, system, serviceName);
}
else {
// 普通 sdu。灰度时可能计算过客户号这里就不重复计算
if (routingUnit == null) {
routingUnit = TencentUnitManager.getRoutingUnit(customerIdentifier);
}
if (checkUnitIdAndSetContext(routingUnit, system, customerIdentifier, serviceName)) {
// 增加上下文记录目标单元号和目标的shardingKey传递到下游
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_CUSTOMER_NUMBER, routingUnit.getCustomerNumber());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SHARDING_KEY, routingUnit.getShardingKey());
}
else {
// sdu 没有对应 ns前面 gdu 没有对应实例,预期后面会提示没有实例的错误
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] do calculate customer num. service name:{}, unit context:{}",
serviceName, TencentUnitContext.getCompositeContextMap());
}
}
}
public static boolean checkSystem(String system, String serviceName) {
// 有业务系统时,才需要判断
if (StringUtils.isEmpty(system)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] system is empty, serviceName:{}", serviceName);
}
return false;
}
if (!TencentUnitManager.getBusinessSystemSet().contains(system)) {
String msg = String.format("[preRequestRecordUnitContext] system is empty or not exist:%s, serviceName:%s", system, serviceName);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
return true;
}
public static boolean preRequestRecordIfGduNotInLocalCloud(String serviceName) {
String gduUnitId = getGduUnitId();
boolean gduInLocalCloud = TencentUnitUtils.checkLocalCloudSpace(gduUnitId);
if (gduInLocalCloud) {
return false;
}
setCrossGduContext(gduUnitId, serviceName);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] gdu in other cloud, gdu unit id:{}, service name:{}, unit context:{}",
gduUnitId, serviceName, TencentUnitContext.getCompositeContextMap());
}
return true;
}
public static boolean preRequestRecordIfDestUnitExist(String system, String customerIdentifier, String serviceName) {
String destUnitId = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
if (StringUtils.isNotEmpty(destUnitId)) {
String failoverUnitId = TencentUnitManager.getFailoverUnitIdMap().get(destUnitId);
if (StringUtils.isNotEmpty(failoverUnitId)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] use failover unit id:{}, origin unit id:{}, service name:{}",
failoverUnitId, destUnitId, serviceName);
}
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID, failoverUnitId);
destUnitId = failoverUnitId;
}
if (checkUnitIdAndSetContext(destUnitId, system, serviceName)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordUnitContext] unit id exist:{}. service name:{}, unit context:{}",
destUnitId, serviceName, TencentUnitContext.getCompositeContextMap());
}
return true;
}
else {
String msg = String.format("[preRequestRecordUnitContext] destUnitId ns not exist:%s, customerIdentifier:%s,"
+ " system:%s, serviceName:%s", destUnitId, customerIdentifier, system, serviceName);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
}
return false;
}
/**
* unit context .
* @return RoutingUnit
*/
public static RoutingUnit preRequestRecordGrayUnitContext() {
String sourceGrayUnitInfo = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO);
if (StringUtils.isEmpty(sourceGrayUnitInfo) && CollectionUtils.isEmpty(TencentUnitManager.getGrayUnitRoutes())) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordGrayUnitContext] empty header gray info and empty gray unit routes,"
+ "unitContextMap:{}", TencentUnitContext.getCompositeContextMap());
}
return null;
}
if (StringUtils.isNotEmpty(sourceGrayUnitInfo)) {
// 已经是灰度单元的,直接转
List<GrayMatchRouteUnit> grayMatchRouteUnitList = TencentUnitContext.parseGrayMatchRouteUnitList();
TencentUnitContext.setGrayUnitContext(grayMatchRouteUnitList);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordGrayUnitContext] using header gray, unitContextMap:{}",
TencentUnitContext.getCompositeContextMap());
}
return null;
}
// 判断是否存在潜在的 gray context如果不存在则一定不是灰度
if (TencentUnitContext.grayContextIsEmpty()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordGrayUnitContext] empty gray header, unitContextMap:{}",
TencentUnitContext.getCompositeContextMap());
}
return null;
}
// 存在灰度规则的相关 header判断是否匹配
GrayMatchRoute grayMatchRoute = null;
RoutingUnit routingUnit = null;
String customerIdentifier = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER);
for (GrayUnitRoute grayUnitRoute : TencentUnitManager.getGrayUnitRoutes()) {
if (UnitTagEngine.checkGrayUnitRouteHit(grayUnitRoute)) {
boolean match = false;
if (Boolean.TRUE.equals(grayUnitRoute.getMatch().getGrayListEnabled())) {
// 还需要命中需要灰度名单
if (StringUtils.isNotEmpty(customerIdentifier)) {
if (routingUnit == null) {
// 客户号只获取一次
routingUnit = TencentUnitManager.getRoutingUnit(customerIdentifier);
}
if (routingUnit != null && TencentUnitManager.getUnitGrayIds()
.contains(routingUnit.getCustomerNumber())) {
match = true;
}
else {
LOGGER.debug("[preRequestRecordGrayUnitContext] match routes, not in gray list");
}
}
}
else {
match = true;
}
if (match) {
grayMatchRoute = grayUnitRoute.getRoute();
break;
}
}
}
// 匹配
if (grayMatchRoute != null) {
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO,
JacksonUtils.serialize2Json(grayMatchRoute.getUnits()));
TencentUnitContext.setGrayUnitContext(grayMatchRoute.getUnits());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[preRequestRecordGrayUnitContext] grayMatchRoute:{}, unitContextMap:{}",
grayMatchRoute, TencentUnitContext.getCompositeContextMap());
}
return routingUnit;
}
/**
* gdu ns null ns ns serviceName ns nsId/serviceName.
*/
public static String getGduServiceId(String serviceName, String gduNsId) {
if (StringUtils.isEmpty(gduNsId)) {
return null;
}
if (TencentUnitManager.checkLocalNamespace(gduNsId)) {
return serviceName;
}
else {
return gduNsId + "/" + serviceName;
}
}
public static String getGduUnitId() {
String gduUnitId;
if (StringUtils.isNotEmpty(TencentUnitContext.getGraySystemTag(TencentUnitContext.GRAY_CLOUD_SPACE_GDU_UNIT_ID))) {
gduUnitId = TencentUnitContext.getGraySystemTag(TencentUnitContext.GRAY_CLOUD_SPACE_GDU_UNIT_ID);
}
else {
gduUnitId = TencentUnitManager.getGduUnitId();
}
return gduUnitId;
}
/**
* 使consumerfeignrestTemplate/ cloud gw headernstoken.
*/
public static void processFromUnitHeader(Map<String, String> unitMap, String path) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[getProxyInfoFromUnitHeader] parse unit from header. path:{}, origin unit map:{}", path, unitMap);
}
String targetCloudId = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD);
if (TencentUnitManager.isLocalCloudSpace(targetCloudId)) {
updateLocalCloudUnitContext(unitMap);
}
else {
updateCrossCloudUnitContext(unitMap);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[getProxyInfoFromUnitHeader] parse unit from header. path:{}, unit context:{}",
path, TencentUnitContext.getCompositeContextMap());
}
}
/**
* cloud gw.
*/
private static void updateCrossCloudUnitContext(Map<String, String> unitMap) {
String destUnitId = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
String destServiceName = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE);
String destSystem = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM);
if (!TencentUnitManager.checkUnitId(destUnitId)) {
// 非当前 cloud, 只需要检查是否存在
String msg = String.format("[getProxyInfoFromUnitHeader] check unit id error, destUnitId:%s, destServiceName:%s",
destUnitId, destServiceName);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
unitMap.put(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD, TencentUnitManager.getUnitCloudMap().get(destUnitId));
Gateway gateway = getGateway(destUnitId, destSystem);
TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY, gateway);
TencentUnitContext.putSystemTags(unitMap);
}
/**
* cloud .
*/
private static void updateLocalCloudUnitContext(Map<String, String> unitMap) {
String destUnitId = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
String destServiceName = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE);
if (StringUtils.isEmpty(destUnitId)) {
TencentUnitContext.putSystemTags(unitMap);
// 是否跨 cloud gdu 优先访问,此时没有目标单元
String grayUnitInfo = unitMap.get(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO);
// 设置灰度上游
if (StringUtils.isNotEmpty(grayUnitInfo)) {
TencentUnitContext.putSourceTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO, grayUnitInfo);
}
SpringCloudUnitUtils.preRequestRecordUnitContext(destServiceName);
}
else {
String destSystemName = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM);
// 在当前 cloud, check ns
UnitNamespace namespace = TencentUnitManager.getUnitNamespaceId(destUnitId, destSystemName);
if (namespace == null) {
String msg = String.format("[getProxyInfoFromUnitHeader] check ns error, destUnitId:%s, destServiceName:%s",
destUnitId, destServiceName);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
TencentUnitContext.putSystemTags(unitMap);
TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID, namespace.getId());
}
}
/**
* 使httpheader cid .
*/
public static void processFromCid(String cid, String path) {
//url/groupContext/system/ms/api
String serviceName;
String systemName;
String[] pathSegments = path.split("/");
if (pathSegments.length >= 5) {
serviceName = pathSegments[3];
systemName = pathSegments[2];
if (StringUtils.isEmpty(serviceName) || StringUtils.isEmpty(systemName)) {
String msg = String.format("[checkUnitFromHttp] serviceName:%s or systemName:%s is empty", serviceName, systemName);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
}
else {
String msg = "[checkUnitFromHttp] path pattern is wrong, path:" + path;
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER, cid);
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, systemName);
SpringCloudUnitUtils.preRequestRecordUnitContext(serviceName);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[getProxyInfoFromCid] parse unit from cid. path:{}, unit context:{}",
path, TencentUnitContext.getCompositeContextMap());
}
}
private static boolean checkUnitIdAndSetContext(RoutingUnit routingUnit, String system, String customerIdentifier, String serviceName) {
if (routingUnit == null || StringUtils.isEmpty(routingUnit.getUnitId())) {
String msg = String.format("[preRequestRecordUnitContext] routingUnit unit id not exist, customerIdentifier:%s, system:%s, serviceName:%s", customerIdentifier, system, serviceName);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
return checkUnitIdAndSetContext(routingUnit.getUnitId(), system, serviceName);
}
private static boolean checkUnitIdAndSetContext(String unitId, String system, String serviceName) {
if (TencentUnitUtils.checkLocalCloudSpace(unitId)) {
// 本地,检查 ns 是否存在
UnitNamespace namespace = TencentUnitManager.getUnitNamespaceId(unitId, system);
if (namespace != null) {
setUnitContext(unitId, namespace, serviceName);
// 设置治理 ns
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace.getId());
return true;
}
else {
return false;
}
}
else if (TencentUnitManager.checkUnitId(unitId)) {
// 异地,检查 unitId 是否存在
setUnitContext(unitId, serviceName);
return true;
}
else {
return false;
}
}
private static void setUnitContext(String unitId, String serviceName) {
setUnitContext(unitId, null, serviceName);
}
private static void setUnitContext(String unitId, UnitNamespace namespace, String serviceName) {
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID, unitId);
if (namespace != null) {
TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID, namespace.getId());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_NAMESPACE_ID, namespace.getId());
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_NAMESPACE_NAME, namespace.getName());
}
// 不同 cloud, 需要转到对应 gateway
if (!TencentUnitUtils.checkLocalCloudSpace(unitId)) {
Gateway gateway = getGateway(unitId);
TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY, gateway);
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE, serviceName);
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD, TencentUnitManager.getUnitCloudMap()
.get(unitId));
}
}
/**
* cloud gdu , gdu first 访.
*/
private static void setCrossGduContext(String unitId, String serviceName) {
Gateway gateway = getGateway(unitId);
TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY, gateway);
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE, serviceName);
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD, TencentUnitManager.getUnitCloudMap()
.get(unitId));
}
private static Gateway getGateway(String unitId) {
return getGateway(unitId, TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM));
}
private static Gateway getGateway(String unitId, String system) {
String targetGatewayCloudId = TencentUnitManager.getUnitCloudMap().get(unitId);
if (!EnvironmentUtils.isGateway()) {
targetGatewayCloudId = TencentUnitManager.getLocalCloudSpaceId();
}
Gateway result = TencentUnitManager.getBusinessSystemGateway(targetGatewayCloudId, system);
if (result == null) {
String msg = String.format("[getGateway] gateway not exist, targetGatewayCloudId:%s, unitId:%s, system:%s", targetGatewayCloudId, unitId, system);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
}
return result;
}
}

@ -25,15 +25,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
import com.tencent.cloud.plugin.unit.config.UnitAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration({UnitAutoConfiguration.class})
public @interface EnableTsfUnit {
}

@ -24,12 +24,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited

@ -24,12 +24,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Empty annotation. Compatible with old versions TSF SDK.
*
* @author Haotian Zhang
*/
@Deprecated(since = "2.0.0.0")
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Inherited

@ -0,0 +1,100 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import com.tencent.tsf.unit.annotation.TsfUnitCall;
import com.tencent.tsf.unit.annotation.TsfUnitCustomerIdentifier;
import com.tencent.tsf.unit.core.utils.TencentUnitUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* .
*/
@Aspect
public class TsfUnitRouteAspect {
private static final Logger LOG = LoggerFactory.getLogger(TsfUnitRouteAspect.class);
public TsfUnitRouteAspect() {
LOG.info("init TsfUnitRouteAspect");
}
// 拦截@FeignClient+@TsfUnitCall的对象
@Pointcut("@within(com.tencent.tsf.unit.annotation.TsfUnitCall) &&" +
"@within(org.springframework.cloud.openfeign.FeignClient)")
public void unitRoutePointcut() {
}
@Around("unitRoutePointcut()")
public Object unitRouteProcess(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 获取拦截点pointcut的函数签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取拦截点的类名
String pointClassName = methodSignature.getDeclaringTypeName();
// 获取拦截点所属类名的Class
Class<?> pointCutClass = Class.forName(pointClassName);
// 获取拦截点所在类Class上的注解获取本地调用注解@TsfUnitCall
TsfUnitCall tsfUnitCallClass = pointCutClass.getAnnotation(TsfUnitCall.class);
// 获取单元化注解上的systemName
String systemName = tsfUnitCallClass.systemName();
boolean global = tsfUnitCallClass.global();
// 获取在拦截点对象上执行的方法
Method pointCutMethod = methodSignature.getMethod();
// 获取方法上所有参数的注解,找到@TsfUnitUserTag的修饰的参数作为用户标签客户号取出来
String cid = getCustomerIdentifier(pointCutMethod.getParameterAnnotations(), joinPoint.getArgs());
// 写入Context
TencentUnitUtils.putCustomerTags(systemName, cid, global);
return joinPoint.proceed();
}
catch (Throwable e) {
LOG.error("[TsfUnitRoute] aspect catch exception: " + e.getMessage());
throw e;
}
}
// 获取参数里面里有userTag相关注解的参数
private String getCustomerIdentifier(Annotation[][] annotations, Object[] args) {
// 找到TsfUnitUserTag注解的位置
int index = -1;
for (int i = 0; i < annotations.length; i++) {
for (Annotation annotation : annotations[i]) {
if (annotation.annotationType() == TsfUnitCustomerIdentifier.class) {
index = i;
break;
}
}
if (index >= 0) {
break;
}
}
if (index >= 0 && index < args.length) {
Object object = args[index];
if (object.getClass().equals(String.class)) {
return (String) object;
}
}
return null;
}
}

@ -0,0 +1,61 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import com.tencent.polaris.api.utils.IPAddressUtils;
public final class Env {
private Env() {
}
private final static String consulToken;
private final static String consulHost;
private final static Integer consulPort;
private final static String namespaceId;
static {
// 只支持从环境变量取
consulHost = IPAddressUtils.getIpCompatible(getSystemProperty("tsf_consul_ip", "localhost"));
consulPort = Integer.parseInt(getSystemProperty("tsf_consul_port", "8500"));
consulToken = getSystemProperty("tsf_token", "");
namespaceId = getSystemProperty("tsf_namespace_id", "");
}
private static String getSystemProperty(String name, String defaultValue) {
String val = null;
if (System.getenv(name) != null) {
val = System.getenv(name);
}
if (System.getProperty(name) != null) {
val = System.getProperty(name);
}
return (val == null) ? defaultValue : val;
}
public static String getConsulToken() {
return consulToken;
}
public static String getConsulHost() {
return consulHost;
}
public static Integer getConsulPort() {
return consulPort;
}
public static String getNamespaceId() {
return namespaceId;
}
}

@ -0,0 +1,81 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.Map;
import java.util.Objects;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.model.UnitArch.Gateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GatewayUnitArchCallback implements IUnitChangeCallback {
private static final Logger LOGGER = LoggerFactory.getLogger(GatewayUnitArchCallback.class);
private final String applicationName;
public GatewayUnitArchCallback(String applicationName) {
this.applicationName = applicationName;
}
@Override
public void callback() {
boolean enableUnit = false;
for (Map.Entry<String, Gateway> entry : TencentUnitManager.getLocalBusinessSystemGwMap().entrySet()) {
Gateway gateway = entry.getValue();
if (StringUtils.equals(applicationName, gateway.getServiceName())
&& StringUtils.equals(Env.getNamespaceId(), gateway.getNamespaceId())) {
enableUnit = true;
break;
}
}
if (!enableUnit) {
for (Map.Entry<String, Gateway> entry: TencentUnitManager.getLocalOnlyGwMap().entrySet()) {
Gateway gateway = entry.getValue();
if (StringUtils.equals(applicationName, gateway.getServiceName())
&& StringUtils.equals(Env.getNamespaceId(), gateway.getNamespaceId())) {
enableUnit = true;
break;
}
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[callback] enableUnit:{}, applicationName:{}, ns:{}, localBusinessSystemGwMap:{}, localOnlyGwMap:{}",
enableUnit, applicationName, Env.getNamespaceId(), TencentUnitManager.getLocalBusinessSystemGwMap(), TencentUnitManager.getLocalOnlyGwMap());
}
if (TencentUnitManager.isEnable() != enableUnit) {
LOGGER.info("[callback] unit enable change, new status:{}", enableUnit);
}
TencentUnitManager.setEnable(enableUnit);
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GatewayUnitArchCallback that)) {
return false;
}
return StringUtils.equals(applicationName, that.applicationName) && StringUtils.equals(getName(), that.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), applicationName);
}
}

@ -0,0 +1,18 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
public interface IUnitChangeCallback {
void callback();
String getName();
}

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.Iterator;
import java.util.ServiceLoader;
import com.tencent.tsf.unit.core.mapping.api.IMappingService;
import com.tencent.tsf.unit.core.mapping.impl.CustomerMappingService;
/**
* MappingService.
*/
public final class MappingServiceLoader {
private MappingServiceLoader() {
}
private static IMappingService service;
static {
ServiceLoader<IMappingService> mappingServices = ServiceLoader.load(IMappingService.class);
if (mappingServices != null) {
Iterator<IMappingService> itr = mappingServices.iterator();
while (itr.hasNext()) {
service = itr.next();
}
}
// 默认实现
if (service == null) {
service = new CustomerMappingService();
}
}
public static IMappingService getService() {
return service;
}
}

@ -0,0 +1,433 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.type.TypeReference;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayMatchRouteUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TencentUnitContext {
/**
* key CloudSpace .
*/
public static final String CLOUD_SPACE_TARGET_UNIT_ID = "CloudSpaceTargetUnitId";
/**
* gdu true/false.
*/
public static final String CLOUD_SPACE_GDU_FORWARD_ONLY = "CloudSpaceGduFrowardOnly";
/**
* sdu true/false.
*/
public static final String CLOUD_SPACE_SDU_FORWARD_ONLY = "CloudSpaceSduFrowardOnly";
/**
* CLOUD_SPACE_GDU_INSTANCE_EXIST.
*/
public static final String CLOUD_SPACE_GDU_INSTANCE_EXIST = "CloudSpaceGduInstanceExist";
/**
* cloud space.
*/
public static final String CLOUD_SPACE_TARGET_CLOUD = "CloudSpaceTargetCloud";
/**
* ns id gdu forward only ns gdu ns.
*/
public static final String CLOUD_SPACE_TARGET_NAMESPACE_ID = "CloudSpaceTargetNamespaceId";
/**
* CLOUD_SPACE_TARGET_NAMESPACE_NAME.
*/
public static final String CLOUD_SPACE_TARGET_NAMESPACE_NAME = "CloudSpaceTargetNamespaceName";
/**
* .
*/
public static final String CLOUD_SPACE_CUSTOMER_IDENTIFIER = "CloudSpaceCustomerIdentifier";
/**
* .
*/
public static final String CLOUD_SPACE_TARGET_SYSTEM = "CloudSpaceTargetSystem";
/**
* gw, ms.
*/
public static final String CLOUD_SPACE_TARGET_SERVICE = "CloudSpaceTargetService";
/**
* GrayMatchRouteUnit list json .
*/
public static final String CLOUD_SPACE_GRAY_UNIT_INFO = "CloudSpaceGrayUnitInfo";
/**
* GrayCloudSpaceGduUnitId.
*/
public static final String GRAY_CLOUD_SPACE_GDU_UNIT_ID = "GrayCloudSpaceGduUnitId";
/**
* GrayCloudSpaceSduUnitId.
*/
public static final String GRAY_CLOUD_SPACE_SDU_UNIT_ID = "GrayCloudSpaceSduUnitId";
/**
* .
*/
public static final String CLOUD_SPACE_TARGET_CUSTOMER_NUMBER = "CloudSpaceTargetCustomerNumber";
/**
* ShardingKeyShardingKey.
*/
public static final String CLOUD_SPACE_TARGET_SHARDING_KEY = "CloudSpaceTargetShardingKey";
/**
* value .
*/
public static final String CLOUD_SPACE_ROUTE_GATEWAY = "CloudSpaceRouteGateway";
/**
* CloudSpaceRouteTargetNamespaceId.
*/
public static final String CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID = "CloudSpaceRouteTargetNamespaceId";
/**
* user context system context key.
*/
public static final Set<String> CLOUD_SPACE_SYSTEM_FROM_USER_KEYS = new HashSet<>(
Arrays.asList(CLOUD_SPACE_TARGET_SYSTEM, CLOUD_SPACE_CUSTOMER_IDENTIFIER, CLOUD_SPACE_TARGET_UNIT_ID));
/**
* SOURCE_PREFIX.
*/
public static final String SOURCE_PREFIX = "source.";
/**
* GRAY_PREFIX.
*/
public static final String GRAY_PREFIX = "gray.";
private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitContext.class);
// 用户标签,客户可以直接设置的
private final static ThreadLocal<HashMap<String, String>> USER_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
// 系统标签,用于中间的计算,部分需要传递
private final static ThreadLocal<HashMap<String, String>> SYSTEM_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
// 路由标签, key 为 CloudSpaceRoute 前缀的,提供给 TsfConsulReactiveCommonDiscoveryClient 做服务发现使用,无需传递
private final static ThreadLocal<HashMap<String, Object>> ROUTE_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
// 上游标签,不需要放到 header 传递
private final static ThreadLocal<HashMap<String, String>> SOURCE_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
// 用户灰度标签,用于匹配灰度规则,不需要放到 header 传递
private final static ThreadLocal<HashMap<String, String>> GRAY_USER_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
// 灰度内部标签,不需要放到 header 传递
private final static ThreadLocal<HashMap<String, String>> GRAY_SYSTEM_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
private TencentUnitContext() {
}
@Deprecated
public static void putTag(String key, String value) {
putUserTag(key, value);
}
public static void putUserTag(String key, String value) {
USER_CONTEXTS.get().put(key, value);
}
public static void putSystemTag(String key, String value) {
SYSTEM_CONTEXTS.get().put(key, value);
}
public static void putRouteTag(String key, Object value) {
ROUTE_CONTEXTS.get().put(key, value);
}
public static void putSourceTag(String key, String value) {
SOURCE_CONTEXTS.get().put(SOURCE_PREFIX + key, value);
}
public static void putGrayUserTags(String position, Map<String, String> labels) {
for (Map.Entry<String, String> label : labels.entrySet()) {
putGrayUserTag(position, label.getKey(), label.getValue());
}
}
// 需要携带 position做灰度路由匹配用的
public static void putGrayUserTag(String position, String key, String value) {
GRAY_USER_CONTEXTS.get().put(getGrayPositionPrefix(position) + key, value);
}
// 传递灰度单元信息用的
public static void putGraySystemTag(String key, String value) {
GRAY_SYSTEM_CONTEXTS.get().put(getGrayPrefix() + key, value);
}
public static String getGraySystemTag(String key) {
return GRAY_SYSTEM_CONTEXTS.get().get(getGrayPrefix() + key);
}
public static boolean grayContextIsEmpty() {
return GRAY_USER_CONTEXTS.get().isEmpty();
}
public static String getGrayPositionPrefix(String position) {
return GRAY_PREFIX + position.toLowerCase(Locale.ROOT) + ".";
}
public static String getGrayPrefix() {
return GRAY_PREFIX;
}
public static void putSystemTagsFromUser() {
for (String key : CLOUD_SPACE_SYSTEM_FROM_USER_KEYS) {
if (USER_CONTEXTS.get().containsKey(key)) {
SYSTEM_CONTEXTS.get().put(key, USER_CONTEXTS.get().get(key));
}
}
}
public static void putSystemTags(Map<String, String> tags) {
for (Map.Entry<String, String> tag : tags.entrySet()) {
SYSTEM_CONTEXTS.get().put(tag.getKey(), tag.getValue());
}
}
public static void putSourceTags(Map<String, String> tags) {
for (Map.Entry<String, String> tag : tags.entrySet()) {
putSourceTag(tag.getKey(), tag.getValue());
}
}
@Deprecated
public static String getTag(String key) {
return getUserTag(key);
}
public static String getUserTag(String key) {
return USER_CONTEXTS.get().get(key);
}
public static String getSystemTag(String key) {
return SYSTEM_CONTEXTS.get().get(key);
}
public static Object getObjectRouteTag(String key) {
return ROUTE_CONTEXTS.get().get(key);
}
public static String getStringRouteTag(String key) {
return (String) ROUTE_CONTEXTS.get().get(key);
}
public static boolean containRouteTag(String key) {
return ROUTE_CONTEXTS.get().containsKey(key);
}
public static void removeAll() {
USER_CONTEXTS.get().clear();
SYSTEM_CONTEXTS.get().clear();
ROUTE_CONTEXTS.get().clear();
SOURCE_CONTEXTS.get().clear();
GRAY_USER_CONTEXTS.get().clear();
GRAY_SYSTEM_CONTEXTS.get().clear();
}
public static void clearGrayUserContext() {
GRAY_USER_CONTEXTS.get().clear();
}
/**
* key.
*/
public static void clearUserTags() {
USER_CONTEXTS.get().remove(CLOUD_SPACE_TARGET_UNIT_ID);
USER_CONTEXTS.get().remove(CLOUD_SPACE_CUSTOMER_IDENTIFIER);
USER_CONTEXTS.get().remove(CLOUD_SPACE_TARGET_SYSTEM);
}
public static void setUnitCompositeContextMap(UnitCompositeContextMap unitCompositeContextMap) {
removeAll();
USER_CONTEXTS.get().putAll(unitCompositeContextMap.getUserContext());
SYSTEM_CONTEXTS.get().putAll(unitCompositeContextMap.getSystemContext());
SOURCE_CONTEXTS.get().putAll(unitCompositeContextMap.getSourceContext());
GRAY_USER_CONTEXTS.get().putAll(unitCompositeContextMap.getGrayUserContext());
GRAY_SYSTEM_CONTEXTS.get().putAll(unitCompositeContextMap.getGraySystemContext());
}
public static UnitCompositeContextMap getCompositeContextMap() {
return new UnitCompositeContextMap(USER_CONTEXTS.get(), SYSTEM_CONTEXTS.get(), ROUTE_CONTEXTS.get(),
SOURCE_CONTEXTS.get(), GRAY_USER_CONTEXTS.get(), GRAY_SYSTEM_CONTEXTS.get());
}
public static String getSourceTag(String key) {
return SOURCE_CONTEXTS.get().get(SOURCE_PREFIX + key);
}
public static String getGrayTag(String position, String key) {
return GRAY_USER_CONTEXTS.get().get(getGrayPositionPrefix(position) + key);
}
public static List<GrayMatchRouteUnit> parseGrayMatchRouteUnitList() {
String json = getSourceTag(CLOUD_SPACE_GRAY_UNIT_INFO);
List<GrayMatchRouteUnit> result = null;
if (StringUtils.isNotEmpty(json)) {
result = JacksonUtils.deserialize(json, new TypeReference<List<GrayMatchRouteUnit>>() { });
}
return result;
}
public static void setGrayUnitContext(List<GrayMatchRouteUnit> grayMatchRouteUnitList) {
// 目前应该只有一个 gdu 和 sdu
if (grayMatchRouteUnitList != null && grayMatchRouteUnitList.size() == 2) {
// 需要根据 unit id + system 才能获取 ns 信息,这里设置 unit id
putGraySystemTag(GRAY_CLOUD_SPACE_GDU_UNIT_ID, grayMatchRouteUnitList.get(0).getId());
putGraySystemTag(GRAY_CLOUD_SPACE_SDU_UNIT_ID, grayMatchRouteUnitList.get(1).getId());
// 以 gray 信息为准,优先级高,清空可能存在的 target unit id
putSystemTag(CLOUD_SPACE_TARGET_UNIT_ID, null);
}
else {
LOGGER.warn("[setGrayUnitContext] gray route format error:{}", grayMatchRouteUnitList);
}
}
public static class UnitCompositeContextMap {
private Map<String, String> userContext;
private Map<String, String> systemContext;
private Map<String, Object> routeContext;
private Map<String, String> sourceContext;
private Map<String, String> grayUserContext;
private Map<String, String> graySystemContext;
public UnitCompositeContextMap() {
this.userContext = Collections.emptyMap();
this.systemContext = Collections.emptyMap();
this.routeContext = Collections.emptyMap();
this.sourceContext = Collections.emptyMap();
this.grayUserContext = Collections.emptyMap();
this.graySystemContext = Collections.emptyMap();
}
public UnitCompositeContextMap(Map<String, String> userContext,
Map<String, String> systemContext, Map<String, Object> routeContext, Map<String, String> sourceContext,
Map<String, String> grayUserContext, Map<String, String> graySystemContext) {
this.userContext = userContext;
this.systemContext = systemContext;
this.routeContext = routeContext;
this.sourceContext = sourceContext;
this.grayUserContext = grayUserContext;
this.graySystemContext = graySystemContext;
}
public Map<String, String> getUserContext() {
return userContext;
}
public void setUserContext(Map<String, String> userContext) {
this.userContext = userContext;
}
public Map<String, String> getSystemContext() {
return systemContext;
}
public void setSystemContext(Map<String, String> systemContext) {
this.systemContext = systemContext;
}
public Map<String, Object> getRouteContext() {
return routeContext;
}
public void setRouteContext(Map<String, Object> routeContext) {
this.routeContext = routeContext;
}
public Map<String, String> getSourceContext() {
return sourceContext;
}
public void setSourceContext(Map<String, String> sourceContext) {
this.sourceContext = sourceContext;
}
public Map<String, String> getGrayUserContext() {
return grayUserContext;
}
public void setGrayUserContext(Map<String, String> grayUserContext) {
this.grayUserContext = grayUserContext;
}
public Map<String, String> getGraySystemContext() {
return graySystemContext;
}
public void setGraySystemContext(Map<String, String> graySystemContext) {
this.graySystemContext = graySystemContext;
}
public String getSystemTag(String key) {
return systemContext.get(key);
}
public String getRouteStringTag(String key) {
if (routeContext.containsKey(key)) {
return String.valueOf(routeContext.get(key));
}
else {
return null;
}
}
public boolean containRouteTag(String key) {
return routeContext.containsKey(key);
}
public Object getRouteTag(String key) {
return routeContext.get(key);
}
public String getSourceTag(String key) {
return sourceContext.get(SOURCE_PREFIX + key);
}
public String getGraySystemTag(String key) {
return graySystemContext.get(GRAY_PREFIX + key);
}
public String getGrayTag(String position, String key) {
return grayUserContext.get(getGrayPositionPrefix(position) + key);
}
public boolean isForwardToGateway() {
return routeContext.containsKey(CLOUD_SPACE_ROUTE_GATEWAY);
}
public boolean containTargetNamespaceId() {
return routeContext.containsKey(CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID);
}
@Override
public String toString() {
return "UnitCompositeContextMap{" +
"userContext=" + userContext +
", systemContext=" + systemContext +
", routeContext=" + routeContext +
", sourceContext=" + sourceContext +
", grayUserContext=" + grayUserContext +
", graySystemContext=" + graySystemContext +
'}';
}
}
}

@ -0,0 +1,925 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.algorithm.HashCodeAlgorithm;
import com.tencent.tsf.unit.core.algorithm.IUnitTransformAlgorithm;
import com.tencent.tsf.unit.core.algorithm.ModAlgorithm;
import com.tencent.tsf.unit.core.algorithm.SubstrAlgorithm;
import com.tencent.tsf.unit.core.exception.ErrorCode;
import com.tencent.tsf.unit.core.exception.TencentUnitException;
import com.tencent.tsf.unit.core.mapping.api.MappingEntity;
import com.tencent.tsf.unit.core.model.RoutingUnit;
import com.tencent.tsf.unit.core.model.UnitArch;
import com.tencent.tsf.unit.core.model.UnitArch.CloudSpace;
import com.tencent.tsf.unit.core.model.UnitArch.DeploymentUnit;
import com.tencent.tsf.unit.core.model.UnitArch.Gateway;
import com.tencent.tsf.unit.core.model.UnitArch.Gdu;
import com.tencent.tsf.unit.core.model.UnitArch.Sdu;
import com.tencent.tsf.unit.core.model.UnitArch.TencentUnitArch;
import com.tencent.tsf.unit.core.model.UnitArch.UnitCloudArch;
import com.tencent.tsf.unit.core.model.UnitGray;
import com.tencent.tsf.unit.core.model.UnitGray.TencentUnitGray;
import com.tencent.tsf.unit.core.model.UnitGray.UnitGrayList;
import com.tencent.tsf.unit.core.model.UnitNamespace;
import com.tencent.tsf.unit.core.model.UnitRouteInfo;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRoute;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRouteRule;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.MappingService;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.MatchRoute;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.MatchRouteFailover;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.RouterIdentifier;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.TagTransform;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.TencentUnitRouteRule;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.TransformAction;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.UnitRoute;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.UnitRouteRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TencentUnitManager {
/**
* DEFAULT_KEY_SEPARATOR.
*/
public static final String DEFAULT_KEY_SEPARATOR = ",";
/**
* DEFAULT_KEY_VALUE_SEPARATOR.
*/
public static final String DEFAULT_KEY_VALUE_SEPARATOR = "=";
/**
* DEFAULT_SHARDING_IDENTIFIER_KEY.
*/
public static final String DEFAULT_SHARDING_IDENTIFIER_KEY = "CustomerNumber";
/**
* DEFAULT_ROUTER_IDENTIFIER_HEADER.
*/
public static final String DEFAULT_ROUTER_IDENTIFIER_HEADER = "Router-Identifier";
private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitManager.class);
private static final List<IUnitChangeCallback> archCallbacks = new ArrayList<>();
private static final ReadWriteLock archCallbackRwLock = new ReentrantReadWriteLock();
private static final Lock archCallbackRLock = archCallbackRwLock.readLock();
private static final Lock archCallbackWLock = archCallbackRwLock.writeLock();
private static final List<IUnitChangeCallback> ruleCallbacks = new ArrayList<>();
private static final ReadWriteLock ruleCallbackRwLock = new ReentrantReadWriteLock();
private static final Lock ruleCallbackRLock = ruleCallbackRwLock.readLock();
private static final Lock ruleCallbackWLock = ruleCallbackRwLock.writeLock();
private volatile static UnitArch unitArch;
private volatile static UnitRouteInfo unitRouteInfo;
// 灰名单列表
private volatile static Set<String> unitGrayIds = new HashSet<>();
private volatile static List<GrayUnitRoute> grayUnitRoutes;
private volatile static Set<String> grayUnitHeaderKey = new HashSet<>();
private volatile static String localCloudSpaceId = "";
private volatile static String localUnitId = null;
private volatile static String localGduUnitId = null;
private volatile static String localFailoverGduUnitId = null;
// all cloud space
private volatile static List<CloudSpace> allCloudSpaces = new ArrayList<>();
// gdu unit id -> Gdu
private volatile static Map<String, Gdu> allGduMap = new ConcurrentHashMap<>();
// gray unit id -> du(gdu/sdu)
private volatile static Map<String, DeploymentUnit> grayUnitDuMap = new ConcurrentHashMap<>();
// gdu unit id -> { system -> ns }
private volatile static Map<String, Map<String, UnitNamespace>> gduNsMap = new ConcurrentHashMap<>();
// gray gdu unit id -> { system -> ns }
private volatile static Map<String, Map<String, UnitNamespace>> grayGduNsMap = new HashMap<>();
// (cloud id + ns id) -> unit id
private volatile static Map<String, String> nsUnitMap = new ConcurrentHashMap<>();
// unit id -> cloud id
private volatile static Map<String, String> unitCloudMap = new ConcurrentHashMap<>();
// (sdu unit id + business system name) -> gdu unit id
private volatile static Map<String, String> businessSystemGduMap = new ConcurrentHashMap<>();
// (gdu/sdu unit id + business system name) -> ns
private volatile static Map<String, UnitNamespace> businessSystemNsMap = new ConcurrentHashMap<>();
// cloud id -> { business system name -> gw}
private volatile static Map<String, Map<String, Gateway>> businessSystemGwMap = new ConcurrentHashMap<>();
// cloud id -> { gw id -> local only gw}
private volatile static Map<String, Map<String, Gateway>> localOnlyGwMap = new ConcurrentHashMap<>();
// (gdu/sdu unit id) -> failover gdu/sdu unit id
private volatile static Map<String, String> failoverUnitIdMap = new ConcurrentHashMap<>();
private volatile static Map<String, Boolean> activeSduIdMap = new ConcurrentHashMap<>();
private volatile static Set<String> disableZoneSet = new HashSet<>();
private volatile static Set<String> businessSystemSet = new HashSet<>();
// 一期只有一个
private volatile static TagTransform tagTransform = null;
private volatile static List<UnitRoute> unitRoutes = Collections.emptyList();
private volatile static CloudSpace localCloudSpace;
private volatile static boolean enable = false;
// 是否透传 unit context默认 false
private volatile static boolean contextPassingThroughEnabled = false;
private volatile static RouterIdentifier routerIdentifier = new RouterIdentifier(
DEFAULT_KEY_SEPARATOR, DEFAULT_KEY_VALUE_SEPARATOR, DEFAULT_SHARDING_IDENTIFIER_KEY, DEFAULT_ROUTER_IDENTIFIER_HEADER);
/**
* .
*/
private volatile static IUnitTransformAlgorithm customAlgorithm;
private TencentUnitManager() {
}
public static UnitArch getUnitArch() {
return unitArch;
}
public static void setUnitArch(UnitArch unitArch) {
if (!checkUnitArch(unitArch)) {
throw new TencentUnitException(ErrorCode.LOAD_ERROR, "parse unit arch exception");
}
TencentUnitManager.unitArch = unitArch;
Optional.ofNullable(unitArch).map(UnitArch::getTencent).map(TencentUnitArch::getUnitCloudArchitecture).
map(UnitCloudArch::getLocalCloudId).ifPresent(id -> localCloudSpaceId = id);
Optional<List<CloudSpace>> optionalCloudSpaces = Optional.ofNullable(unitArch).
map(UnitArch::getTencent).map(TencentUnitArch::getUnitCloudArchitecture).
map(UnitCloudArch::getCloudSpaces);
optionalCloudSpaces.ifPresent(cloudSpaces -> {
Map<String, String> newNsUnitMap = new ConcurrentHashMap<>();
Map<String, String> newUnitCloudMap = new ConcurrentHashMap<>();
Map<String, String> newBusinessSystemGduMap = new ConcurrentHashMap<>();
Map<String, UnitNamespace> newBusinessSystemNsMap = new ConcurrentHashMap<>();
Map<String, Map<String, Gateway>> newBusinessSystemGwMap = new ConcurrentHashMap<>();
Map<String, Map<String, Gateway>> newLocalOnlyGwMap = new ConcurrentHashMap<>();
Map<String, Map<String, UnitNamespace>> newGrayGduNsMap = new ConcurrentHashMap<>();
Map<String, Map<String, UnitNamespace>> newGduNsMap = new ConcurrentHashMap<>();
Map<String, DeploymentUnit> newGrayUnitDuMap = new ConcurrentHashMap<>();
Map<String, Gdu> newAllGduMap = new ConcurrentHashMap<>();
for (CloudSpace cloudSpace : cloudSpaces) {
String gduUnitId = "";
String grayGduUnitId = "";
// 目前一套 cloud space 下应该最多只有一个 gdu
if (cloudSpace.getGdus() != null) {
for (Gdu gdu : cloudSpace.getGdus()) {
gduUnitId = gdu.getId();
newAllGduMap.put(gdu.getId(), gdu);
Map<String, UnitNamespace> tmpSystemNsMap = new ConcurrentHashMap<>();
if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
for (UnitNamespace namespace : gdu.getNamespaces()) {
newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
namespace.getId()), gdu.getId());
tmpSystemNsMap.put(namespace.getBusinessSystemName(), namespace);
newBusinessSystemNsMap.put(getUnitBusinessSystem(
gdu.getId(), namespace.getBusinessSystemName()), namespace);
}
}
newUnitCloudMap.put(gdu.getId(), cloudSpace.getCloudId());
if (cloudSpace.getCloudId().equals(localCloudSpaceId)) {
localGduUnitId = gduUnitId;
}
newGduNsMap.put(gdu.getId(), tmpSystemNsMap);
}
}
if (cloudSpace.getSdus() != null) {
for (Sdu sdu : cloudSpace.getSdus()) {
if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
for (UnitNamespace namespace : sdu.getNamespaces()) {
newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
namespace.getId()), sdu.getId());
// 如果 cloud space 下没有 gdu对应 gduUnitId 为默认值空字符串
newBusinessSystemGduMap.put(getUnitBusinessSystem(
sdu.getId(), namespace.getBusinessSystemName()), gduUnitId);
newBusinessSystemNsMap.put(getUnitBusinessSystem(
sdu.getId(), namespace.getBusinessSystemName()), namespace);
}
}
newUnitCloudMap.put(sdu.getId(), cloudSpace.getCloudId());
}
}
if (cloudSpace.getGrayGdus() != null) {
for (Gdu grayGdu : cloudSpace.getGrayGdus()) {
// 未来如果有多个 gray gdu则下面处理 gray sdu 时需要额外处理
grayGduUnitId = grayGdu.getId();
newGrayUnitDuMap.put(grayGduUnitId, grayGdu);
Map<String, UnitNamespace> tmpSystemNsMap = new ConcurrentHashMap<>();
if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
for (UnitNamespace namespace : grayGdu.getNamespaces()) {
newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
namespace.getId()), grayGdu.getId());
tmpSystemNsMap.put(namespace.getBusinessSystemName(), namespace);
newBusinessSystemNsMap.put(getUnitBusinessSystem(
grayGdu.getId(), namespace.getBusinessSystemName()), namespace);
}
}
newUnitCloudMap.put(grayGdu.getId(), cloudSpace.getCloudId());
newGrayGduNsMap.put(grayGdu.getId(), tmpSystemNsMap);
}
}
if (cloudSpace.getGraySdus() != null) {
for (Sdu graySdu : cloudSpace.getGraySdus()) {
newGrayUnitDuMap.put(graySdu.getId(), graySdu);
if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
for (UnitNamespace namespace : graySdu.getNamespaces()) {
newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
namespace.getId()), graySdu.getId());
// 如果 cloud space 下没有 gdu对应 gduUnitId 为默认值空字符串
newBusinessSystemGduMap.put(getUnitBusinessSystem(
graySdu.getId(), namespace.getBusinessSystemName()), grayGduUnitId);
newBusinessSystemNsMap.put(getUnitBusinessSystem(
graySdu.getId(), namespace.getBusinessSystemName()), namespace);
}
}
newUnitCloudMap.put(graySdu.getId(), cloudSpace.getCloudId());
}
}
// 优先读新配置
if (cloudSpace.getScopeGateways() != null) {
for (Gateway gateway : cloudSpace.getScopeGateways()) {
UnitArch.RouteScope routeScope = Optional.ofNullable(gateway.getRouteScope()).orElse(UnitArch.RouteScope.LOCAL_REMOTE);
switch (routeScope) {
case LOCAL:
newLocalOnlyGwMap.putIfAbsent(cloudSpace.getCloudId(), new ConcurrentHashMap<>());
newLocalOnlyGwMap.get(cloudSpace.getCloudId()).put(gateway.getId(), gateway);
break;
default:
newBusinessSystemGwMap.putIfAbsent(cloudSpace.getCloudId(), new ConcurrentHashMap<>());
newBusinessSystemGwMap.get(cloudSpace.getCloudId()).put(gateway.getBusinessSystemName(), gateway);
break;
}
}
}
else if (cloudSpace.getGateways() != null) {
for (Gateway gateway : cloudSpace.getGateways()) {
newBusinessSystemGwMap.putIfAbsent(cloudSpace.getCloudId(), new ConcurrentHashMap<>());
newBusinessSystemGwMap.get(cloudSpace.getCloudId())
.put(gateway.getBusinessSystemName(), gateway);
}
}
if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
localCloudSpace = cloudSpace;
}
}
allCloudSpaces = cloudSpaces;
nsUnitMap = newNsUnitMap;
unitCloudMap = newUnitCloudMap;
businessSystemGduMap = newBusinessSystemGduMap;
businessSystemNsMap = newBusinessSystemNsMap;
businessSystemGwMap = newBusinessSystemGwMap;
localOnlyGwMap = newLocalOnlyGwMap;
localUnitId = nsUnitMap.get(getCloudSpaceNamespaceId(localCloudSpaceId, Env.getNamespaceId()));
grayGduNsMap = newGrayGduNsMap;
gduNsMap = newGduNsMap;
grayUnitDuMap = newGrayUnitDuMap;
allGduMap = newAllGduMap;
});
Optional.ofNullable(unitArch).
map(UnitArch::getTencent).map(TencentUnitArch::getUnitCloudArchitecture).
map(UnitCloudArch::getBusinessSystems).ifPresent(businessSystemList -> {
Set<String> newBusinessNameSet = new HashSet<>();
for (UnitArch.BusinessSystem businessSystem : businessSystemList) {
newBusinessNameSet.add(businessSystem.getName());
}
businessSystemSet = newBusinessNameSet;
});
loadArchCallback();
}
private static void loadArchCallback() {
archCallbackRLock.lock();
for (IUnitChangeCallback callback : archCallbacks) {
try {
callback.callback();
}
catch (Exception e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[setUnitArch] arch callback:{}, error msg:{}",
callback.getName(), e.getMessage(), e);
}
else {
LOGGER.warn("[setUnitArch] arch callback:{}, error msg:{}",
callback.getName(), e.getMessage());
}
}
}
archCallbackRLock.unlock();
}
public static UnitRouteInfo getUnitRouteRule() {
return unitRouteInfo;
}
public static void setUnitRouteRule(UnitRouteInfo unitRouteInfo) {
if (!checkUnitRouteRule(unitRouteInfo)) {
throw new TencentUnitException(ErrorCode.LOAD_ERROR, "parse unit route exception");
}
TencentUnitManager.unitRouteInfo = unitRouteInfo;
Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent).
map(TencentUnitRouteRule::getUnitRouteRule).
map(UnitRouteRule::getPassingThroughEnabled).ifPresent(throughEnabled -> {
contextPassingThroughEnabled = throughEnabled;
});
// tagTransforms 目前最多只有一个list.get(0) 取第一个
Optional<TransformAction> optionalTransformAction = Optional.ofNullable(unitRouteInfo).
map(UnitRouteInfo::getTencent).map(TencentUnitRouteRule::getUnitRouteRule).
map(UnitRouteRule::getTagTransforms).filter(list -> list.size() > 0).
map(list -> list.get(0)).map(TagTransform::getTransform);
optionalTransformAction.ifPresent(transformAction -> {
if (transformAction.getAlgorithm() != null) {
TransformAlgorithmEnum algorithmEnum =
TransformAlgorithmEnum.getTransformAlgorith(transformAction.getAlgorithm().getName());
IUnitTransformAlgorithm unitTransformAlgorithm = null;
switch (algorithmEnum) {
case HASHCODE:
unitTransformAlgorithm = new HashCodeAlgorithm(transformAction.getAlgorithm()
.getOptions());
break;
case MOD:
unitTransformAlgorithm = new ModAlgorithm(transformAction.getAlgorithm()
.getOptions());
break;
case SUBSTR:
unitTransformAlgorithm = new SubstrAlgorithm(transformAction.getAlgorithm()
.getOptions());
break;
}
// TODO: 2023/11/3 支持自定义算法
transformAction.setUnitTransformAlgorithm(unitTransformAlgorithm);
tagTransform = unitRouteInfo.getTencent().getUnitRouteRule().getTagTransforms().get(0);
unitRoutes = unitRouteInfo.getTencent().getUnitRouteRule().getUnitRoutes();
}
});
Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent)
.map(TencentUnitRouteRule::getGrayUnitRouteRule).map(
GrayUnitRouteRule::getUnitRoutes).ifPresent(grayUnitRouteList -> {
grayUnitRoutes = grayUnitRouteList;
Set<String> newGrayUnitHeaderKey = new HashSet<>();
for (GrayUnitRoute route : grayUnitRoutes) {
for (UnitTag unitTag : route.getMatch().getConditions()) {
switch (unitTag.getTagPosition()) {
case HEADER:
newGrayUnitHeaderKey.add(unitTag.getTagField());
break;
}
}
}
grayUnitHeaderKey = newGrayUnitHeaderKey;
}
);
Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent).
map(TencentUnitRouteRule::getRouterIdentifier).ifPresent(identifier -> {
if (StringUtils.isEmpty(identifier.getKeySeparator())) {
identifier.setKeySeparator(DEFAULT_KEY_SEPARATOR);
}
if (StringUtils.isEmpty(identifier.getKeyValueSeparator())) {
identifier.setKeyValueSeparator(DEFAULT_KEY_VALUE_SEPARATOR);
}
if (StringUtils.isEmpty(identifier.getShardingIdentifierKey())) {
identifier.setShardingIdentifierKey(DEFAULT_SHARDING_IDENTIFIER_KEY);
}
if (StringUtils.isEmpty(identifier.getRouterIdentifierHeader())) {
identifier.setRouterIdentifierHeader(DEFAULT_ROUTER_IDENTIFIER_HEADER);
}
Map<String, MappingService> mappingServiceMap = new HashMap<>();
if (identifier.getShardingIdentifierMappingServices() != null) {
for (MappingService mappingService : identifier.getShardingIdentifierMappingServices()) {
mappingServiceMap.put(mappingService.getCloudId(), mappingService);
}
}
identifier.setMappingServiceMap(mappingServiceMap);
routerIdentifier = identifier;
});
Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent).ifPresent(tencentUnitRouteRule -> {
Map<String, String> newFailoverUnitIdMap = new ConcurrentHashMap<>();
Map<String, Boolean> newActiveSduIdMap = new ConcurrentHashMap<>();
Set<String> newDisableZoneSet = new HashSet<>();
String newLocalFailoverGduUnitId = null;
if (tencentUnitRouteRule.getGduRouteRule() != null) {
for (UnitRoute gduRouteRule : tencentUnitRouteRule.getGduRouteRule()) {
// 暂时都是只有一个 CloudId, operator 只有 and
Optional<UnitTag> matchTag = gduRouteRule.getMatch().getConditions().stream().
filter(cond -> UnitTagEngine.matchTag(cond, localCloudSpaceId)).findFirst();
if (matchTag.isPresent()) {
String scope = Optional.ofNullable(gduRouteRule).map(UnitRoute::getRoute)
.map(MatchRoute::getFailover).map(MatchRouteFailover::getScope).orElse("");
switch (scope) {
case "region":
if (Boolean.TRUE.equals(gduRouteRule.getRoute().getFailover().getEnabled())) {
LOGGER.info("use gdu failover mode, match name:{}, origin gdu route unit:{}, failover unit:{}",
gduRouteRule.getMatch().getName(), gduRouteRule.getRoute()
.getUnitId(), gduRouteRule.getRoute().getFailover().getRoute()
.getUnitId());
newLocalFailoverGduUnitId = gduRouteRule.getRoute().getFailover().getRoute()
.getUnitId();
newFailoverUnitIdMap.put(gduRouteRule.getRoute()
.getUnitId(), newLocalFailoverGduUnitId);
}
break;
case "zone":
for (UnitTag unitTag : gduRouteRule.getRoute().getFailover().getRoute().getLocationFilter()
.getZoneFilter().getConditions()) {
newDisableZoneSet.add(unitTag.getTagValue());
}
LOGGER.info("use zone failover mode, match name:{}, newDisableZoneSet:{}", gduRouteRule.getMatch()
.getName(), newDisableZoneSet);
break;
}
break;
}
}
}
if (tencentUnitRouteRule.getUnitRouteRule() != null) {
for (UnitRoute unitRoute : tencentUnitRouteRule.getUnitRouteRule().getUnitRoutes()) {
MatchRouteFailover failover = unitRoute.getRoute().getFailover();
if (failover != null && Boolean.TRUE.equals(failover.getEnabled())) {
newFailoverUnitIdMap.put(unitRoute.getRoute().getUnitId(), failover.getRoute().getUnitId());
newActiveSduIdMap.put(failover.getRoute().getUnitId(), true);
}
else {
newActiveSduIdMap.put(unitRoute.getRoute().getUnitId(), true);
}
}
}
failoverUnitIdMap = newFailoverUnitIdMap;
disableZoneSet = newDisableZoneSet;
localFailoverGduUnitId = newLocalFailoverGduUnitId;
activeSduIdMap = newActiveSduIdMap;
});
loadRuleCallback();
}
private static void loadRuleCallback() {
ruleCallbackRLock.lock();
try {
for (IUnitChangeCallback callback : ruleCallbacks) {
callback.callback();
}
}
finally {
ruleCallbackRLock.unlock();
}
}
public static Map<String, String> getFailoverUnitIdMap() {
return failoverUnitIdMap;
}
public static void setUnitGray(UnitGray unitGray) {
Optional.ofNullable(unitGray).map(UnitGray::getTencent).map(TencentUnitGray::getUnitGrayList)
.map(UnitGrayList::getIds).ifPresent(ids -> {
unitGrayIds = new HashSet<>(ids);
});
}
public static Set<String> getUnitGrayIds() {
return unitGrayIds;
}
public static IUnitTransformAlgorithm getCustomAlgorithm() {
return customAlgorithm;
}
public static void setCustomAlgorithm(IUnitTransformAlgorithm customAlgorithm) {
TencentUnitManager.customAlgorithm = customAlgorithm;
}
public static String getCloudSpaceNamespaceId(String cloudSpaceId, String namespaceId) {
return String.format("cn:%s@%s", cloudSpaceId, namespaceId);
}
public static String getUnitBusinessSystem(String unitId, String businessSystemName) {
return String.format("ub:%s@%s", unitId, businessSystemName);
}
public static String getCloudBusinessSystem(String cloudSpaceId, String businessSystemName) {
return String.format("cb:%s@%s", cloudSpaceId, businessSystemName);
}
public static String getLocalCloudSpaceId() {
return localCloudSpaceId;
}
public static boolean isLocalCloudSpace(String targetCloudId) {
return StringUtils.equals(targetCloudId, getLocalCloudSpaceId());
}
public static String getLocalUnitId() {
return localUnitId;
}
public static String getGduUnitId() {
if (StringUtils.isNotEmpty(localFailoverGduUnitId)) {
return localFailoverGduUnitId;
}
else {
return localGduUnitId;
}
}
public static TagTransform getTagTransform() {
return tagTransform;
}
public static Map<String, String> getNsUnitMap() {
return nsUnitMap;
}
public static Map<String, String> getUnitCloudMap() {
return unitCloudMap;
}
public static Map<String, String> getBusinessSystemGduMap() {
return businessSystemGduMap;
}
public static List<UnitRoute> getUnitRoutes() {
return unitRoutes;
}
public static CloudSpace getLocalCloudSpace() {
return localCloudSpace;
}
/**
* unit id cloud .
*/
public static boolean checkUnitId(String unitId) {
return getUnitCloudMap().containsKey(unitId);
}
/**
* customerIdentifier( unitRoutes ).
* RoutingUnitnull
*/
public static RoutingUnit getRoutingUnit(String customerIdentifier) {
// 根据客户要素routerIdentifier获取客户号
String customerNumber = TencentUnitManager.getCustomerNumber(customerIdentifier);
// 根据客户号进行hash等计算得到转换值分片号
String targetTagValue = getTransformTargetTagValue(customerNumber);
if (targetTagValue == null) {
LOGGER.warn("[unit] customerIdentifier:{}, customerNumber: {}, transfer to target tag is null",
customerIdentifier, customerNumber);
return null;
}
// 根据分片信息计算出匹配的单元信息
MatchRoute matchRoute = getMatchRoute(customerIdentifier, customerNumber, targetTagValue);
if (matchRoute != null) {
// 返回路由单元信息包含了中间计算的数据便于上层调用方回溯和使用RoutingUnitContext会使用
return new RoutingUnit(customerNumber, targetTagValue, matchRoute);
}
LOGGER.warn("[unit] customerIdentifier: {}, customerNumber: {}, not match any rule",
customerIdentifier, customerNumber);
return null;
}
/**
* taValuesharingKeyMatchRoute.
*
* @param customerIdentifier
* @param customerNumber
* @param tagValue
* @return null
*/
public static MatchRoute getMatchRoute(String customerIdentifier, String customerNumber, String tagValue) {
// 从路由规则里获取tagName
String tagName = Optional.ofNullable(getTagTransform()).
map(tagTransform -> tagTransform.getDestination()).
map(destination -> destination.getTagName()).orElse(null);
// 路由匹配
Map<String, String> targetTagMap = new HashMap<>();
targetTagMap.put(tagName, tagValue);
for (UnitRoute route : getUnitRoutes()) {
if (UnitTagEngine.checkUnitRouteHit(route, targetTagMap)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[checkUnitRouteListHit] customer({}, {}) tag({}) match route unit id:{}",
customerIdentifier, customerNumber, targetTagMap, route.getRoute().getActualUnitId());
}
return route.getRoute();
}
}
// 没有匹配到规则的 tag返回null由上层决定是否抛出异常
return null;
}
public static String getTransformTargetTagValue(String cid) {
return Optional.ofNullable(getTagTransform()).
map(tagTransform -> tagTransform.getTransform()).
map(transform -> transform.getUnitTransformAlgorithm()).
map(algorithm -> algorithm.transform(cid)).orElse(null);
}
/**
* .
* k1=v1,k2=v2,k3=v3k=CustomerNumber
*
* @param cid
* @return String
*/
public static String getCustomerNumber(String cid) {
// 解释k1=v1,k2=v2...
String[] kvList = StringUtils.split(cid, routerIdentifier.getKeySeparator());
// 找一下是否存在CustomerNumber
Map<String, String> kvMap = new HashMap<>(kvList.length);
for (String value : kvList) {
String[] kv = StringUtils.split(value, routerIdentifier.getKeyValueSeparator(), 2);
if (kv.length != 2) {
String msg = String.format("customerIdentifier(%s) format error, key separator:(%s), key value separator:(%s)",
cid, routerIdentifier.getKeySeparator(), routerIdentifier.getKeyValueSeparator());
throw new TencentUnitException(ErrorCode.CUSTOMER_IDENTIFIER_TRANSFORM_ERROR, msg);
}
kvMap.put(kv[0], kv[1]);
}
if (kvMap.containsKey(routerIdentifier.getShardingIdentifierKey())) {
return kvMap.get(routerIdentifier.getShardingIdentifierKey());
}
if (MappingServiceLoader.getService() == null) {
String msg = String.format("[unit] cid:%s, not found key(%s) and not found mapping service", cid, routerIdentifier.getShardingIdentifierKey());
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.MAPPING_SERVICE_EMPTY_ERROR, msg);
}
MappingEntity message = MappingServiceLoader.getService().processMapping(cid);
if (message == null || StringUtils.isEmpty(message.getCustomerNumber())) {
// 如果使用的是 CustomerMappingService这里只会是 empty customer number
String msg = String.format("invoke mapping service get empty customer, cid:%s", cid);
LOGGER.error(msg);
throw new TencentUnitException(ErrorCode.MAPPING_RESPONSE_CUSTOMER_NUMBER_EMPTY_ERROR, msg);
}
return message.getCustomerNumber();
}
/**
* cloud .
*/
public static UnitNamespace getUnitNamespaceId(String unitId, String systemName) {
// businessSystemNsMap 包含 gdu 和 sdu 的
return businessSystemNsMap.get(getUnitBusinessSystem(unitId, systemName));
}
public static boolean isEnable() {
return enable;
}
public static void setEnable(boolean enable) {
TencentUnitManager.enable = enable;
}
public static boolean checkLocalNamespace(String nsId) {
return StringUtils.equals(Env.getNamespaceId(), nsId);
}
public static Gateway getBusinessSystemGateway(String cloudId, String system) {
return businessSystemGwMap.getOrDefault(cloudId, Collections.emptyMap()).get(system);
}
public static Gateway getLocalBusinessSystemGateway(String system) {
return businessSystemGwMap.getOrDefault(getLocalCloudSpaceId(), Collections.emptyMap()).get(system);
}
public static Map<String, Gateway> getLocalBusinessSystemGwMap() {
return businessSystemGwMap.getOrDefault(getLocalCloudSpaceId(), Collections.emptyMap());
}
public static Map<String, Gateway> getLocalOnlyGwMap() {
return localOnlyGwMap.getOrDefault(getLocalCloudSpaceId(), Collections.emptyMap());
}
public static void addArchCallback(IUnitChangeCallback callback) {
archCallbackWLock.lock();
try {
if (!archCallbacks.contains(callback)) {
archCallbacks.add(callback);
}
}
finally {
archCallbackWLock.unlock();
}
loadArchCallback();
loadRuleCallback();
}
public static void addRuleCallback(IUnitChangeCallback callback) {
ruleCallbackWLock.lock();
try {
if (!ruleCallbacks.contains(callback)) {
ruleCallbacks.add(callback);
}
}
finally {
ruleCallbackWLock.unlock();
}
loadArchCallback();
loadRuleCallback();
}
public static RouterIdentifier getRouterIdentifier() {
return routerIdentifier;
}
public static Map<String, Map<String, UnitNamespace>> getGrayGduNsMap() {
return grayGduNsMap;
}
public static Map<String, Map<String, UnitNamespace>> getGduNsMap() {
return gduNsMap;
}
public static Set<String> getBusinessSystemSet() {
return businessSystemSet;
}
public static String getRouterIdentifierHeader() {
if (routerIdentifier != null) {
return routerIdentifier.getRouterIdentifierHeader();
}
else {
return DEFAULT_ROUTER_IDENTIFIER_HEADER;
}
}
public static String getLocalCloudMappingApi() {
String result = Optional.ofNullable(routerIdentifier).map(RouterIdentifier::getMappingServiceMap)
.map(m -> m.get(localCloudSpaceId)).map(MappingService::getApiPath).orElse(null);
// sdk 兼容 mapping url 从 arch 获取
if (StringUtils.isEmpty(result)) {
result = Optional.ofNullable(TencentUnitManager.getLocalCloudSpace()).
map(CloudSpace::getMappingServiceUrl).orElse(null);
}
return result;
}
public static List<GrayUnitRoute> getGrayUnitRoutes() {
return grayUnitRoutes;
}
public static boolean inGrayUnit() {
return grayUnitDuMap.containsKey(getLocalUnitId());
}
public static String getGraySduUnitId() {
List<Sdu> graySdus = Optional.ofNullable(localCloudSpace)
.map(CloudSpace::getGraySdus).orElse(Collections.emptyList());
for (Sdu graySdu : graySdus) {
// 不为空时返回第一个
return graySdu.getId();
}
return null;
}
public static Set<String> getGrayUnitHeaderKey() {
return grayUnitHeaderKey;
}
public static boolean isContextPassingThroughEnabled() {
return contextPassingThroughEnabled;
}
private static boolean checkUnitArch(UnitArch unitArch) {
try {
String localCloudId = unitArch.getTencent().getUnitCloudArchitecture().getLocalCloudId();
if (StringUtils.isEmpty(localCloudId)) {
LOGGER.warn("parse unit arch error, empty localCloudId");
return false;
}
Set<String> cloudIds = new HashSet<>();
for (CloudSpace cloudSpace : unitArch.getTencent().getUnitCloudArchitecture().getCloudSpaces()) {
if (StringUtils.isEmpty(cloudSpace.getCloudId())) {
LOGGER.warn("parse unit arch error, empty cloudId");
return false;
}
cloudIds.add(cloudSpace.getCloudId());
}
if (!cloudIds.contains(localCloudId)) {
LOGGER.warn("parse unit arch error, localCloudId:{} not in cloudIds:{}", localCloudId, cloudIds);
return false;
}
return true;
}
catch (Exception e) {
LOGGER.warn("parse unit arch exception:{}", e.getMessage());
return false;
}
}
private static boolean checkUnitRouteRule(UnitRouteInfo unitRouteInfo) {
try {
AtomicBoolean pass = new AtomicBoolean(true);
Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent)
.map(TencentUnitRouteRule::getGrayUnitRouteRule).map(
GrayUnitRouteRule::getUnitRoutes).ifPresent(grayUnitRouteList -> {
for (GrayUnitRoute route : grayUnitRouteList) {
for (UnitTag unitTag : route.getMatch().getConditions()) {
switch (unitTag.getTagPosition()) {
// 暂时只支持 header
case HEADER:
break;
default:
LOGGER.warn("parse unit tag position error, not support:{} ", unitTag.getTagPosition());
pass.set(false);
}
}
if (route.getRoute().getUnits().size() != 2) {
LOGGER.warn("gray match route units count error");
pass.set(false);
}
}
}
);
// 开启 failover打印日志
Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent)
.map(TencentUnitRouteRule::getUnitRouteRule)
.map(UnitRouteRule::getUnitRoutes).ifPresent(unitRoutesList -> {
for (UnitRoute unitRoute : unitRoutesList) {
MatchRouteFailover failover = unitRoute.getRoute().getFailover();
if (failover != null && Boolean.TRUE.equals(failover.getEnabled())) {
LOGGER.info("use failover mode, match name:{}, origin route unit:{}, failover unit:{}",
unitRoute.getMatch().getName(), unitRoute.getRoute().getUnitId(), failover.getRoute()
.getUnitId());
}
}
});
return pass.get();
}
catch (Exception e) {
LOGGER.warn("parse unit arch exception:{}", e.getMessage());
return false;
}
}
/**
* cloud space GDU.
*/
public static String getGlobalUnitId(String systemName) {
return TencentUnitManager.getBusinessSystemGduMap().get(
TencentUnitManager.getUnitBusinessSystem(
TencentUnitManager.getLocalUnitId(), systemName));
}
public static Set<String> getDisableZoneSet() {
return disableZoneSet;
}
public static Map<String, Gdu> getAllGduMap() {
return allGduMap;
}
public static List<CloudSpace> getAllCloudSpaces() {
return allCloudSpaces;
}
public static Map<String, Boolean> getActiveSduIdMap() {
return activeSduIdMap;
}
}

@ -0,0 +1,47 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import com.tencent.polaris.api.utils.StringUtils;
public enum TransformAlgorithmEnum {
/**
* hash.
*/
HASHCODE,
/**
* .
*/
MOD,
/**
* .
*/
SUBSTR,
/**
* .
*/
OTHER;
public static TransformAlgorithmEnum getTransformAlgorith(String type) {
if (StringUtils.isEmpty(type)) {
return OTHER;
}
for (TransformAlgorithmEnum transformAlgorithmEnum : TransformAlgorithmEnum.values()) {
if (transformAlgorithmEnum.name().equalsIgnoreCase(type)) {
return transformAlgorithmEnum;
}
}
// 其他的当做 other
return OTHER;
}
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.Objects;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.zonefilter.TsfZoneFilterManager;
public class TsfZoneFilterUnitCallback implements IUnitChangeCallback {
@Override
public void callback() {
TsfZoneFilterManager.setDisabledZoneSet(TencentUnitManager.getDisableZoneSet());
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TsfZoneFilterUnitCallback that)) {
return false;
}
return StringUtils.equals(getName(), that.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName());
}
}

@ -0,0 +1,104 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.Objects;
import com.tencent.tsf.unit.core.model.UnitTagPosition;
public class UnitTag {
private UnitTagPosition tagPosition;
/**
* .
*/
private String tagField;
/**
* .
* UnitTagConstant.OPERATOR
*/
private String tagOperator;
/**
* .
*/
private String tagValue;
public UnitTag() {
}
public UnitTag(String tagField, String tagValue) {
this.tagField = tagField;
this.tagValue = tagValue;
}
@Override
public String toString() {
return "UnitTag{" +
"position=" + tagPosition +
", tagField='" + tagField + '\'' +
", tagOperator='" + tagOperator + '\'' +
", tagValue='" + tagValue + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof UnitTag tag)) {
return false;
}
return Objects.equals(tagPosition, tag.tagPosition) &&
tagField.equals(tag.tagField) &&
tagOperator.equals(tag.tagOperator) &&
tagValue.equals(tag.tagValue);
}
@Override
public int hashCode() {
return Objects.hash(tagField, tagOperator, tagValue);
}
public UnitTagPosition getTagPosition() {
return tagPosition;
}
public void setTagPosition(UnitTagPosition tagPosition) {
this.tagPosition = tagPosition;
}
public String getTagField() {
return tagField;
}
public void setTagField(String tagField) {
this.tagField = tagField;
}
public String getTagOperator() {
return tagOperator;
}
public void setTagOperator(String tagOperator) {
this.tagOperator = tagOperator;
}
public String getTagValue() {
return tagValue;
}
public void setTagValue(String tagValue) {
this.tagValue = tagValue;
}
}

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
public class UnitTagConstant {
/**
* .
*/
public static class TagRuleRelation {
/**
* .
*/
public static final String AND = "AND";
/**
* .
*/
public static final String OR = "OR";
}
/**
* .
*/
public static class OPERATOR {
/**
* .
*/
public static final String IN = "IN";
/**
* .
*/
public static final String NOT_IN = "NOT_IN";
/**
* .
*/
public static final String EQUAL = "EQUAL";
/**
* .
*/
public static final String NOT_EQUAL = "NOT_EQUAL";
/**
* .
*/
public static final String REGEX = "REGEX";
/**
* .
*/
public static final String RANGE = "RANGE";
}
}

@ -0,0 +1,147 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.UnitTagConstant.TagRuleRelation;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRoute;
import com.tencent.tsf.unit.core.model.UnitRouteInfo.UnitRoute;
public final class UnitTagEngine {
private UnitTagEngine() {
}
private static final Map<String, Pattern> PATTERN_CACHE_MAP = new ConcurrentHashMap<>();
public static boolean matchTag(UnitTag tag, String targetTagValue) {
String tagField = tag.getTagField();
String tagOperator = tag.getTagOperator();
String tagValue = tag.getTagValue();
if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.EQUAL)) {
// 匹配关系 等于
return StringUtils.equals(tagValue, targetTagValue);
}
else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.NOT_EQUAL)) {
// 匹配关系 不等于
return !StringUtils.equals(tagValue, targetTagValue);
}
else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.IN)) {
// 匹配关系 包含
Set<String> routeTagValueSet = new HashSet<>(Arrays.asList(tagValue.split("\\s*,\\s*")));
return routeTagValueSet.contains(targetTagValue);
}
else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.NOT_IN)) {
// 匹配关系 不包含
Set<String> routeTagValueSet = new HashSet<>(Arrays.asList(tagValue.split("\\s*,\\s*")));
return !routeTagValueSet.contains(targetTagValue);
}
else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.REGEX)) {
// 如果没有 targetTagValue认为不匹配
if (targetTagValue == null) {
return false;
}
// 匹配关系 正则
Pattern pattern = PATTERN_CACHE_MAP.get(tagValue);
if (pattern == null) {
pattern = Pattern.compile(tagValue);
PATTERN_CACHE_MAP.putIfAbsent(targetTagValue, pattern);
}
return pattern.matcher(targetTagValue).matches();
}
else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.RANGE)) {
// 按,分割tagValue
String[] values = tagValue.split("\\s*,\\s*");
if (values.length != 2) {
return false;
}
try {
// 转换为int进行range匹配
int start = Integer.parseInt(values[0]);
int end = Integer.parseInt(values[1]);
int target = Integer.parseInt(targetTagValue);
if (target >= start && target <= end) {
return true;
}
}
catch (NumberFormatException ex) {
return false;
}
return false;
}
else {
return false;
}
}
/**
* route .
*/
public static boolean checkUnitRouteHit(UnitRoute route, Map<String, String> targetTagMap) {
// condition 之间是 or 关系
if (StringUtils.equalsIgnoreCase(TagRuleRelation.OR, route.getMatch().getOperator())) {
for (UnitTag condition : route.getMatch().getConditions()) {
// 有一组匹配直接返回成功
if (UnitTagEngine.matchTag(condition, targetTagMap.get(condition.getTagField()))) {
return true;
}
}
}
else {
// and 关系
for (UnitTag condition : route.getMatch().getConditions()) {
// 有一组不匹配则直接返回 null
if (!UnitTagEngine.matchTag(condition, targetTagMap.get(condition.getTagField()))) {
return false;
}
}
return true;
}
return false;
}
public static boolean checkGrayUnitRouteHit(GrayUnitRoute route) {
// condition 之间是 or 关系
if (StringUtils.equalsIgnoreCase(TagRuleRelation.OR, route.getMatch().getOperator())) {
for (UnitTag condition : route.getMatch().getConditions()) {
// 有一组匹配直接返回成功
if (UnitTagEngine.matchTag(condition,
TencentUnitContext.getGrayTag(condition.getTagPosition().name(), condition.getTagField()))) {
return true;
}
}
return false;
}
else {
// and 关系
for (UnitTag condition : route.getMatch().getConditions()) {
// 有一组不匹配则直接返回 null
if (!UnitTagEngine.matchTag(condition,
TencentUnitContext.getGrayTag(condition.getTagPosition().name(), condition.getTagField()))) {
return false;
}
}
return true;
}
}
}

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.algorithm;
import java.util.Map;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.TransformAlgorithmEnum;
public class HashCodeAlgorithm implements IUnitTransformAlgorithm {
private final Map<String, Object> options;
private final int mod;
public HashCodeAlgorithm(Map<String, Object> options) {
this.options = options;
mod = Integer.parseInt(String.valueOf(options.get("mod")));
}
@Override
public String getName() {
return TransformAlgorithmEnum.HASHCODE.name();
}
@Override
public String transform(String cid) {
if (StringUtils.isEmpty(cid)) {
return null;
}
return String.valueOf(cid.hashCode() % mod);
}
}

@ -0,0 +1,17 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.algorithm;
public interface IUnitTransformAlgorithm {
String getName();
String transform(String cid);
}

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.algorithm;
import java.util.Map;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.TransformAlgorithmEnum;
import com.tencent.tsf.unit.core.exception.ErrorCode;
import com.tencent.tsf.unit.core.exception.TencentUnitException;
public class ModAlgorithm implements IUnitTransformAlgorithm {
private final Map<String, Object> options;
private final int mod;
public ModAlgorithm(Map<String, Object> options) {
this.options = options;
mod = Integer.parseInt(String.valueOf(options.get("mod")));
}
@Override
public String getName() {
return TransformAlgorithmEnum.MOD.name();
}
@Override
public String transform(String cid) {
if (StringUtils.isEmpty(cid)) {
return null;
}
try {
long id = Long.parseLong(cid);
return String.valueOf(id % mod);
}
catch (Exception e) {
throw new TencentUnitException(ErrorCode.CUSTOMER_NUMBER_TRANSFORM_ERROR, "format invalid, customNumber:" + cid, e);
}
}
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.algorithm;
import java.util.Map;
import com.tencent.tsf.unit.core.TransformAlgorithmEnum;
public class SubstrAlgorithm implements IUnitTransformAlgorithm {
private final Map<String, Object> options;
public SubstrAlgorithm(Map<String, Object> options) {
this.options = options;
}
@Override
public String getName() {
return TransformAlgorithmEnum.SUBSTR.name();
}
@Override
public String transform(String cid) {
return cid;
}
}

@ -0,0 +1,63 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.exception;
public enum ErrorCode {
/**
* .
*/
LOAD_ERROR(10000),
/**
* .
*/
COMMON_PARAMETER_ERROR(10001),
/**
* .
*/
MAPPING_SERVICE_EMPTY_ERROR(10002),
/**
* url .
*/
MAPPING_URL_EMPTY_ERROR(10003),
/**
* .
*/
MAPPING_RESPONSE_CODE_ERROR(10004),
/**
* .
*/
MAPPING_RESPONSE_EMPTY_BODY_ERROR(10005),
/**
* customer number.
*/
MAPPING_RESPONSE_CUSTOMER_NUMBER_EMPTY_ERROR(10006),
/**
* IO .
*/
MAPPING_REQUEST_IO_ERROR(10007),
/**
* .
*/
CUSTOMER_NUMBER_TRANSFORM_ERROR(10008),
/**
* .
*/
CUSTOMER_IDENTIFIER_TRANSFORM_ERROR(10009);
private final int code;
ErrorCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.exception;
public class TencentUnitException extends RuntimeException {
private final ErrorCode code;
public TencentUnitException(ErrorCode code) {
this.code = code;
}
public TencentUnitException(ErrorCode code, String message) {
super(message);
this.code = code;
}
public TencentUnitException(ErrorCode code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public String getMessage() {
StringBuilder builder = new StringBuilder(String.format("ERR-%d(%s): ", code.getCode(), code.name()));
builder.append(super.getMessage());
Throwable cause = getCause();
if (null != cause) {
builder.append(", cause: ").append(cause.getMessage());
}
return builder.toString();
}
public ErrorCode getCode() {
return code;
}
}

@ -0,0 +1,18 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.mapping.api;
/**
* .
*/
public interface IMappingService {
// 数据映射服务接口定义
MappingEntity processMapping(String params);
}

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.mapping.api;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MappingEntity {
// 客户号
private String customerNumber;
public MappingEntity() {
}
public MappingEntity(String customerNumber) {
this.customerNumber = customerNumber;
}
public String getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
@Override
public String toString() {
return "MappingEntity{" +
"customerNumber='" + customerNumber + '\'' +
'}';
}
}

@ -0,0 +1,104 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.mapping.impl;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.exception.ErrorCode;
import com.tencent.tsf.unit.core.exception.TencentUnitException;
import com.tencent.tsf.unit.core.mapping.api.IMappingService;
import com.tencent.tsf.unit.core.mapping.api.MappingEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import shade.polaris.okhttp3.HttpUrl;
import shade.polaris.okhttp3.OkHttpClient;
import shade.polaris.okhttp3.Request;
import shade.polaris.okhttp3.Response;
public class CustomerMappingService implements IMappingService {
private static final String DEFAULT_ROUTER_IDENTIFIER_QUERY_KEY = "RouterIdentifier";
private static final Logger LOG = LoggerFactory.getLogger(CustomerMappingService.class);
// 连接超时=2s,请求超时=2s
private final OkHttpClient client = new OkHttpClient.Builder().
connectTimeout(2, TimeUnit.SECONDS).readTimeout(2, TimeUnit.SECONDS).build();
@Override
public MappingEntity processMapping(String params) {
String mappingUrl = getMappingUrl();
if (StringUtils.isEmpty(mappingUrl)) {
String msg = "[MappingService] mapping url is null";
LOG.warn(msg);
throw new TencentUnitException(ErrorCode.MAPPING_URL_EMPTY_ERROR, msg);
}
// params解析
String url = urlBuild(mappingUrl, params);
Request request = new Request.Builder().url(url).build();
try {
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
String msg = String.format("request fail. url:%s, code:%s, err:%s",
url, response.code(), response.message());
LOG.error("[CustomerMappingService] " + msg);
if (response.body() != null) {
response.body().close();
}
throw new TencentUnitException(ErrorCode.MAPPING_RESPONSE_CODE_ERROR, msg);
}
if (response.body() == null) {
String msg = String.format("response body is null. url:%s", url);
LOG.error("[CustomerMappingService] " + msg);
throw new TencentUnitException(ErrorCode.MAPPING_RESPONSE_EMPTY_BODY_ERROR, msg);
}
ObjectMapper objectMapper = new ObjectMapper();
MappingEntity entity = objectMapper.readValue(response.body().string(), MappingEntity.class);
response.body().close();
return entity;
}
catch (IOException e) {
String msg = String.format("occur IOException. url:%s, err:%s", url, e);
if (LOG.isDebugEnabled()) {
LOG.error("[CustomerMappingService] " + msg, e);
}
else {
LOG.error("[CustomerMappingService] " + msg);
}
throw new TencentUnitException(ErrorCode.MAPPING_REQUEST_IO_ERROR, msg, e);
}
}
private String urlBuild(String mappingUrl, String params) {
if (params == null) {
return mappingUrl;
}
HttpUrl.Builder urlBuilder = HttpUrl.get(mappingUrl).newBuilder();
if (StringUtils.isNotEmpty(params)) {
urlBuilder.addQueryParameter(DEFAULT_ROUTER_IDENTIFIER_QUERY_KEY, params);
}
return urlBuilder.build().toString();
}
/**
* .
*/
public String getMappingUrl() {
return TencentUnitManager.getLocalCloudMappingApi();
}
}

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
/**
* RoutingUnit .
*/
public class RoutingUnit {
// 客户号
private String customerNumber;
// 分片KEY分片号信息
private String shardingKey;
// 匹配的目标单元
private UnitRouteInfo.MatchRoute matchRoute;
public RoutingUnit() {
}
public RoutingUnit(String customerNumber, String shardingKey, UnitRouteInfo.MatchRoute matchRoute) {
this.customerNumber = customerNumber;
this.shardingKey = shardingKey;
this.matchRoute = matchRoute;
}
public String getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
public String getShardingKey() {
return shardingKey;
}
public void setShardingKey(String shardingKey) {
this.shardingKey = shardingKey;
}
public UnitRouteInfo.MatchRoute getMatchRoute() {
return matchRoute;
}
public void setMatchRoute(UnitRouteInfo.MatchRoute matchRoute) {
this.matchRoute = matchRoute;
}
public String getUnitId() {
if (matchRoute == null) {
return null;
}
return matchRoute.getActualUnitId();
}
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
/**
* A -> BB.
*/
public class RoutingUnitContext extends RoutingUnit {
private String unitId;
public RoutingUnitContext(String customerNumber, String shardingKey, String unitId) {
setCustomerNumber(customerNumber);
setShardingKey(shardingKey);
this.unitId = unitId;
}
@Override
public String getUnitId() {
return unitId;
}
public void setUnitId(String unitId) {
this.unitId = unitId;
}
}

@ -0,0 +1,65 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
import com.tencent.tsf.unit.core.exception.ErrorCode;
import com.tencent.tsf.unit.core.exception.TencentUnitException;
/**
* +utils使class.
* ns
*/
public class TargetUnitInfo extends UnitInfo {
// 逻辑分片ID多个逻辑分片ID映射到一个单元号里
private String shardingKey;
// namespace
private String namespaceId;
private String namespaceName;
public TargetUnitInfo(UnitInfo unitInfo) {
if (unitInfo == null) {
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "unit info is invalid(null)");
}
this.setUnitId(unitInfo.getUnitId());
this.setZoneId(unitInfo.getZoneId());
this.setZoneName(unitInfo.getZoneName());
this.setRegionId(unitInfo.getRegionId());
this.setRegionName(unitInfo.getRegionName());
this.setUnitType(unitInfo.getUnitType());
this.setAllNamespaceList(unitInfo.getAllNamespaceList());
this.setAllShardingKeyList(unitInfo.getAllShardingKeyList());
}
public String getShardingKey() {
return shardingKey;
}
public void setShardingKey(String shardingKey) {
this.shardingKey = shardingKey;
}
public String getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(String namespaceId) {
this.namespaceId = namespaceId;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
}

@ -0,0 +1,438 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
import java.util.List;
/**
* iwiki /p/4009047246.
*/
public class UnitArch {
private TencentUnitArch tencent;
public TencentUnitArch getTencent() {
return tencent;
}
public void setTencent(TencentUnitArch tencent) {
this.tencent = tencent;
}
public static class TencentUnitArch {
private UnitCloudArch unitCloudArchitecture;
public UnitCloudArch getUnitCloudArchitecture() {
return unitCloudArchitecture;
}
public void setUnitCloudArchitecture(UnitCloudArch unitCloudArchitecture) {
this.unitCloudArchitecture = unitCloudArchitecture;
}
}
public static class UnitCloudArch {
private String localCloudId;
private List<CloudSpace> cloudSpaces;
private List<BusinessSystem> businessSystems;
public String getLocalCloudId() {
return localCloudId;
}
public void setLocalCloudId(String localCloudId) {
this.localCloudId = localCloudId;
}
public List<CloudSpace> getCloudSpaces() {
return cloudSpaces;
}
public void setCloudSpaces(List<CloudSpace> cloudSpaces) {
this.cloudSpaces = cloudSpaces;
}
public List<BusinessSystem> getBusinessSystems() {
return businessSystems;
}
public void setBusinessSystems(
List<BusinessSystem> businessSystems) {
this.businessSystems = businessSystems;
}
}
public static class CloudSpace {
private String cloudId;
private String cloudName;
private String regionId;
private String RegionName;
private List<Sdu> sdus;
private List<Gdu> gdus;
private List<Sdu> graySdus;
private List<Gdu> grayGdus;
private List<Gateway> gateways;
private List<Gateway> scopeGateways;
private String mappingServiceUrl;
public String getCloudId() {
return cloudId;
}
public void setCloudId(String cloudId) {
this.cloudId = cloudId;
}
public String getCloudName() {
return cloudName;
}
public void setCloudName(String cloudName) {
this.cloudName = cloudName;
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
public String getRegionName() {
return RegionName;
}
public void setRegionName(String regionName) {
RegionName = regionName;
}
public List<Sdu> getSdus() {
return sdus;
}
public void setSdus(List<Sdu> sdus) {
this.sdus = sdus;
}
public List<Gdu> getGdus() {
return gdus;
}
public void setGdus(List<Gdu> gdus) {
this.gdus = gdus;
}
public List<Sdu> getGraySdus() {
return graySdus;
}
public void setGraySdus(List<Sdu> graySdus) {
this.graySdus = graySdus;
}
public List<Gdu> getGrayGdus() {
return grayGdus;
}
public void setGrayGdus(List<Gdu> grayGdus) {
this.grayGdus = grayGdus;
}
public List<Gateway> getGateways() {
return gateways;
}
public void setGateways(List<Gateway> gateways) {
this.gateways = gateways;
}
public List<Gateway> getScopeGateways() {
return scopeGateways;
}
public void setScopeGateways(List<Gateway> scopeGateways) {
this.scopeGateways = scopeGateways;
}
public String getMappingServiceUrl() {
return mappingServiceUrl;
}
public void setMappingServiceUrl(String mappingServiceUrl) {
this.mappingServiceUrl = mappingServiceUrl;
}
}
public static class Sdu extends DeploymentUnit {
private GduRule gduRule;
public GduRule getGduRule() {
return gduRule;
}
public void setGduRule(GduRule gduRule) {
this.gduRule = gduRule;
}
}
public static class Gdu extends DeploymentUnit {
}
public static class DeploymentUnit {
private String id;
private String name;
private String zoneId;
private String zoneName;
private List<UnitNamespace> namespaces;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getZoneId() {
return zoneId;
}
public void setZoneId(String zoneId) {
this.zoneId = zoneId;
}
public String getZoneName() {
return zoneName;
}
public void setZoneName(String zoneName) {
this.zoneName = zoneName;
}
public List<UnitNamespace> getNamespaces() {
return namespaces;
}
public void setNamespaces(List<UnitNamespace> namespaces) {
this.namespaces = namespaces;
}
}
public static class Gateway {
private String id;
private Address address;
private String businessSystemName;
private RouteScope routeScope;
private String serviceName;
private String namespaceId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getBusinessSystemName() {
return businessSystemName;
}
public void setBusinessSystemName(String businessSystemName) {
this.businessSystemName = businessSystemName;
}
public RouteScope getRouteScope() {
return routeScope;
}
public void setRouteScope(RouteScope routeScope) {
this.routeScope = routeScope;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(String namespaceId) {
this.namespaceId = namespaceId;
}
@Override
public String toString() {
return "Gateway{" +
"id='" + id + '\'' +
", address=" + address +
", businessSystemName='" + businessSystemName + '\'' +
", serviceName='" + serviceName + '\'' +
", namespaceId='" + namespaceId + '\'' +
'}';
}
}
public static class Address {
private String host;
private String port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
@Override
public String toString() {
return "Address{" +
"host='" + host + '\'' +
", port='" + port + '\'' +
'}';
}
}
public static class GduRule {
private GduRuleRoute route;
public GduRuleRoute getRoute() {
return route;
}
public void setRoute(GduRuleRoute route) {
this.route = route;
}
}
public static class BusinessSystem {
private String id;
private String name;
private String type;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public static class GduRuleRoute {
private String cloudSpaceId;
private String unitId;
public String getCloudSpaceId() {
return cloudSpaceId;
}
public void setCloudSpaceId(String cloudSpaceId) {
this.cloudSpaceId = cloudSpaceId;
}
public String getUnitId() {
return unitId;
}
public void setUnitId(String unitId) {
this.unitId = unitId;
}
}
public enum RouteScope {
/**
* LOCAL.
*/
LOCAL,
/**
* REMOTE.
*/
REMOTE,
/**
* LOCAL_REMOTE.
*/
LOCAL_REMOTE
}
}

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
import java.util.List;
public class UnitGray {
private TencentUnitGray tencent;
public TencentUnitGray getTencent() {
return tencent;
}
public void setTencent(TencentUnitGray tencent) {
this.tencent = tencent;
}
public static class TencentUnitGray {
private UnitGrayList unitGrayList;
public UnitGrayList getUnitGrayList() {
return unitGrayList;
}
public void setUnitGrayList(UnitGrayList unitGrayList) {
this.unitGrayList = unitGrayList;
}
}
public static class UnitGrayList {
private String version;
private List<String> ids;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
}
}

@ -0,0 +1,115 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
import java.util.List;
/**
* SDUGDUutils使class.
*/
public class UnitInfo {
// 单元号
private String unitId;
// 单元类型 SDU or GDU
private String unitType;
// region
private String regionId;
private String regionName;
// zone
private String zoneId;
private String zoneName;
// 当前单元对应的命名空间列表
private List<UnitNamespace> allNamespaceList;
// 当前单元对应的所有shardingId集合
private List<Integer> allShardingKeyList;
public String getUnitId() {
return unitId;
}
public void setUnitId(String unitId) {
this.unitId = unitId;
}
public String getUnitType() {
return unitType;
}
public void setUnitType(String unitType) {
this.unitType = unitType;
}
public String getZoneId() {
return zoneId;
}
public void setZoneId(String zoneId) {
this.zoneId = zoneId;
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
public String getRegionName() {
return regionName;
}
public void setRegionName(String regionName) {
this.regionName = regionName;
}
public String getZoneName() {
return zoneName;
}
public void setZoneName(String zoneName) {
this.zoneName = zoneName;
}
public List<UnitNamespace> getAllNamespaceList() {
return allNamespaceList;
}
public void setAllNamespaceList(List<UnitNamespace> allNamespaceList) {
this.allNamespaceList = allNamespaceList;
}
public List<Integer> getAllShardingKeyList() {
return allShardingKeyList;
}
public void setAllShardingKeyList(List<Integer> allShardingKeyList) {
this.allShardingKeyList = allShardingKeyList;
}
@Override
public String toString() {
return "UnitInfo{" +
"unitId='" + unitId + '\'' +
", unitType='" + unitType + '\'' +
", regionId='" + regionId + '\'' +
", regionName='" + regionName + '\'' +
", zoneId='" + zoneId + '\'' +
", zoneName='" + zoneName + '\'' +
", allNamespaceList=" + allNamespaceList +
", allShardingKeyList=" + allShardingKeyList +
'}';
}
}

@ -0,0 +1,52 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
public class UnitNamespace {
private String id;
private String name;
private String businessSystemName;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBusinessSystemName() {
return businessSystemName;
}
public void setBusinessSystemName(String businessSystemName) {
this.businessSystemName = businessSystemName;
}
@Override
public String toString() {
return "UnitNamespace{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", businessSystemName='" + businessSystemName + '\'' +
'}';
}
}

@ -0,0 +1,558 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tencent.tsf.unit.core.UnitTag;
import com.tencent.tsf.unit.core.algorithm.IUnitTransformAlgorithm;
public class UnitRouteInfo {
private TencentUnitRouteRule tencent;
public TencentUnitRouteRule getTencent() {
return tencent;
}
public void setTencent(TencentUnitRouteRule tencent) {
this.tencent = tencent;
}
public static class TencentUnitRouteRule {
private UnitRouteRule unitRouteRule;
private List<UnitRoute> gduRouteRule;
private GrayUnitRouteRule grayUnitRouteRule;
private RouterIdentifier routerIdentifier;
public UnitRouteRule getUnitRouteRule() {
return unitRouteRule;
}
public void setUnitRouteRule(UnitRouteRule unitRouteRule) {
this.unitRouteRule = unitRouteRule;
}
public List<UnitRoute> getGduRouteRule() {
return gduRouteRule;
}
public void setGduRouteRule(List<UnitRoute> gduRouteRule) {
this.gduRouteRule = gduRouteRule;
}
public GrayUnitRouteRule getGrayUnitRouteRule() {
return grayUnitRouteRule;
}
public void setGrayUnitRouteRule(
GrayUnitRouteRule grayUnitRouteRule) {
this.grayUnitRouteRule = grayUnitRouteRule;
}
public RouterIdentifier getRouterIdentifier() {
return routerIdentifier;
}
public void setRouterIdentifier(
RouterIdentifier routerIdentifier) {
this.routerIdentifier = routerIdentifier;
}
}
public static class GrayUnitRouteRule {
private List<GrayUnitRoute> unitRoutes;
public List<GrayUnitRoute> getUnitRoutes() {
return unitRoutes;
}
public void setUnitRoutes(
List<GrayUnitRoute> unitRoutes) {
this.unitRoutes = unitRoutes;
}
}
public static class UnitRouteRule {
private String id;
private String name;
private String desc;
private Boolean passingThroughEnabled;
private List<TagTransform> tagTransforms;
private List<UnitRoute> unitRoutes;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Boolean getPassingThroughEnabled() {
return passingThroughEnabled;
}
public void setPassingThroughEnabled(Boolean passingThroughEnabled) {
this.passingThroughEnabled = passingThroughEnabled;
}
public List<TagTransform> getTagTransforms() {
return tagTransforms;
}
public void setTagTransforms(
List<TagTransform> tagTransforms) {
this.tagTransforms = tagTransforms;
}
public List<UnitRoute> getUnitRoutes() {
return unitRoutes;
}
public void setUnitRoutes(List<UnitRoute> unitRoutes) {
this.unitRoutes = unitRoutes;
}
}
public static class TagTransform {
private TagTransformLocation source;
private TransformAction transform;
private TagTransformLocation destination;
public TagTransformLocation getSource() {
return source;
}
public void setSource(TagTransformLocation source) {
this.source = source;
}
public TransformAction getTransform() {
return transform;
}
public void setTransform(TransformAction transform) {
this.transform = transform;
}
public TagTransformLocation getDestination() {
return destination;
}
public void setDestination(TagTransformLocation destination) {
this.destination = destination;
}
}
public static class TagTransformLocation {
private String tagName;
public String getTagName() {
return tagName;
}
public void setTagName(String tagName) {
this.tagName = tagName;
}
}
public static class TransformAction {
private TransformAlgorithm algorithm;
// 解析 algorithm 后放入的实际操作类
@JsonIgnore
private IUnitTransformAlgorithm unitTransformAlgorithm;
public TransformAlgorithm getAlgorithm() {
return algorithm;
}
public void setAlgorithm(TransformAlgorithm algorithm) {
this.algorithm = algorithm;
}
public IUnitTransformAlgorithm getUnitTransformAlgorithm() {
return unitTransformAlgorithm;
}
public void setUnitTransformAlgorithm(
IUnitTransformAlgorithm unitTransformAlgorithm) {
this.unitTransformAlgorithm = unitTransformAlgorithm;
}
}
public static class TransformAlgorithm {
private String name;
// algorithm 的操作算子,由 name 决定
private Map<String, Object> options;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, Object> getOptions() {
return options;
}
public void setOptions(Map<String, Object> options) {
this.options = options;
}
}
public static class GrayUnitRoute {
private GrayUnitRouteMatch match;
private GrayMatchRoute route;
public GrayUnitRouteMatch getMatch() {
return match;
}
public void setMatch(GrayUnitRouteMatch match) {
this.match = match;
}
public GrayMatchRoute getRoute() {
return route;
}
public void setRoute(GrayMatchRoute route) {
this.route = route;
}
}
public static class UnitRoute {
private UnitRouteMatch match;
private MatchRoute route;
public UnitRouteMatch getMatch() {
return match;
}
public void setMatch(UnitRouteMatch match) {
this.match = match;
}
public MatchRoute getRoute() {
return route;
}
public void setRoute(MatchRoute route) {
this.route = route;
}
}
public static class GrayUnitRouteMatch extends UnitRouteMatch {
private Boolean grayListEnabled;
public Boolean getGrayListEnabled() {
return grayListEnabled;
}
public void setGrayListEnabled(Boolean grayListEnabled) {
this.grayListEnabled = grayListEnabled;
}
}
public static class UnitRouteMatch {
private String name;
private String operator;
private List<UnitTag> conditions;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public List<UnitTag> getConditions() {
return conditions;
}
public void setConditions(List<UnitTag> conditions) {
this.conditions = conditions;
}
}
public static class GrayMatchRoute {
private List<GrayMatchRouteUnit> units;
public List<GrayMatchRouteUnit> getUnits() {
return units;
}
public void setUnits(
List<GrayMatchRouteUnit> units) {
this.units = units;
}
}
public static class GrayMatchRouteUnit {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "GrayMatchRouteUnit{" +
"id='" + id + '\'' +
'}';
}
}
public static class MatchRoute {
private String unitId;
private MatchRouteFailover failover;
public String getUnitId() {
return unitId;
}
public void setUnitId(String unitId) {
this.unitId = unitId;
}
public MatchRouteFailover getFailover() {
return failover;
}
public void setFailover(MatchRouteFailover failover) {
this.failover = failover;
}
public String getActualUnitId() {
if (failover != null && failover.getEnabled()) {
return failover.getRoute().getUnitId();
}
else {
return unitId;
}
}
}
public static class MatchRouteFailover {
private Boolean enabled;
private FailoverRoute route;
private String scope;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public FailoverRoute getRoute() {
return route;
}
public void setRoute(FailoverRoute route) {
this.route = route;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
public static class FailoverRoute {
private String unitId;
private FailoverRouteLocationFilter locationFilter;
public String getUnitId() {
return unitId;
}
public void setUnitId(String unitId) {
this.unitId = unitId;
}
public FailoverRouteLocationFilter getLocationFilter() {
return locationFilter;
}
public void setLocationFilter(FailoverRouteLocationFilter locationFilter) {
this.locationFilter = locationFilter;
}
}
public static class FailoverRouteLocationFilter {
private UnitRouteMatch zoneFilter;
public UnitRouteMatch getZoneFilter() {
return zoneFilter;
}
public void setZoneFilter(UnitRouteMatch zoneFilter) {
this.zoneFilter = zoneFilter;
}
}
public static class RouterIdentifier {
private String keySeparator;
private String keyValueSeparator;
private String shardingIdentifierKey;
private String routerIdentifierHeader;
private List<MappingService> shardingIdentifierMappingServices;
@JsonIgnore
private Map<String, MappingService> mappingServiceMap;
public RouterIdentifier() {
}
public RouterIdentifier(String keySeparator, String keyValueSeparator,
String shardingIdentifierKey,
String routerIdentifierHeader) {
this.keySeparator = keySeparator;
this.keyValueSeparator = keyValueSeparator;
this.shardingIdentifierKey = shardingIdentifierKey;
this.routerIdentifierHeader = routerIdentifierHeader;
}
public String getKeySeparator() {
return keySeparator;
}
public void setKeySeparator(String keySeparator) {
this.keySeparator = keySeparator;
}
public String getKeyValueSeparator() {
return keyValueSeparator;
}
public void setKeyValueSeparator(String keyValueSeparator) {
this.keyValueSeparator = keyValueSeparator;
}
public String getShardingIdentifierKey() {
return shardingIdentifierKey;
}
public void setShardingIdentifierKey(String shardingIdentifierKey) {
this.shardingIdentifierKey = shardingIdentifierKey;
}
public String getRouterIdentifierHeader() {
return routerIdentifierHeader;
}
public void setRouterIdentifierHeader(String routerIdentifierHeader) {
this.routerIdentifierHeader = routerIdentifierHeader;
}
public List<MappingService> getShardingIdentifierMappingServices() {
return shardingIdentifierMappingServices;
}
public void setShardingIdentifierMappingServices(
List<MappingService> shardingIdentifierMappingServices) {
this.shardingIdentifierMappingServices = shardingIdentifierMappingServices;
}
public Map<String, MappingService> getMappingServiceMap() {
return mappingServiceMap;
}
public void setMappingServiceMap(
Map<String, MappingService> mappingServiceMap) {
this.mappingServiceMap = mappingServiceMap;
}
}
public static class MappingService {
private String cloudId;
private String apiPath;
public String getCloudId() {
return cloudId;
}
public void setCloudId(String cloudId) {
this.cloudId = cloudId;
}
public String getApiPath() {
return apiPath;
}
public void setApiPath(String apiPath) {
this.apiPath = apiPath;
}
}
}

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum UnitTagPosition {
/**
* HTTP HEADER.
*/
HEADER,
/**
* HTTP QUERY.
*/
QUERY,
/**
* HTTP COOKIE.
*/
COOKIE,
/**
* HTTP PATH.
*/
PATH,
/**
* TSF TAG.
*/
TSF_TAG;
@JsonCreator
public static UnitTagPosition fromString(String key) {
for (UnitTagPosition tagPosition : UnitTagPosition.values()) {
if (tagPosition.name().equalsIgnoreCase(key)) {
return tagPosition;
}
}
return null;
}
}

@ -0,0 +1,21 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.model;
public enum UnitType {
/**
* SDU.
*/
SDU,
/**
* GDU.
*/
GDU
}

@ -0,0 +1,97 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.remote;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.tencent.cloud.common.util.GzipUtil;
import com.tencent.tsf.unit.core.Env;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitArch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TencentUnitArchKVLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitArchKVLoader.class);
/**
* consul .
*/
private final static Integer watchTime = 55;
/**
* .
*/
private static Long index = -1L;
private static String rawContent;
private TencentUnitArchKVLoader() {
}
private static String getUnitArchKey() {
return "unit/cloudArchitecture/data";
}
public static void syncUnitArch() {
String newContent = null;
try {
ConsulClient client = TsfUnitConsulManager.getConsulClient();
if (client == null) {
LOGGER.warn("[syncUnitArch] tsf unit consul client is null");
return;
}
Response<GetValue> response = client.getKVValue(getUnitArchKey(),
Env.getConsulToken(), new QueryParams(watchTime, index));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[syncUnitArch] resp:{}", response);
}
// data not change
if (response.getConsulIndex() == null || index.equals(response.getConsulIndex())) {
return;
}
index = response.getConsulIndex();
// 推空保护,启动后理论上不存在单元化配置被删除的场景
if (response.getValue() != null) {
// 控制台 gzip 压缩加 base64 转换,这里进行解压
newContent = GzipUtil.base64DecodeDecompress(response.getValue().getDecodedValue());
loadUnitArch(newContent);
}
}
catch (Throwable t) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[syncUnitArch] newContent:{} error:", newContent, t);
}
else {
LOGGER.warn("[syncUnitArch] error: {}", t.getMessage());
}
}
}
public static void loadUnitArch(String content) throws JsonProcessingException {
LOGGER.info("[unit] unit arch config old raw content:\n{}", rawContent);
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TencentUnitManager.setUnitArch(mapper.readValue(content, UnitArch.class));
rawContent = content;
LOGGER.info("[unit] unit arch config new raw content:\n{}", rawContent);
}
}

@ -0,0 +1,95 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.remote;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.tencent.cloud.common.util.GzipUtil;
import com.tencent.tsf.unit.core.Env;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitGray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TencentUnitGrayKVLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitGrayKVLoader.class);
/**
* consul .
*/
private final static Integer watchTime = 55;
/**
* .
*/
private static Long index = -1L;
private static String rawContent;
private TencentUnitGrayKVLoader() {
}
private static String getUnitGrayKey() {
return "unit/grayList/data";
}
public static void syncUnitGray() {
String newContent = null;
try {
ConsulClient client = TsfUnitConsulManager.getConsulClient();
if (client == null) {
LOGGER.warn("[syncUnitGray] tsf unit consul client is null");
return;
}
Response<GetValue> response = client.getKVValue(getUnitGrayKey(),
Env.getConsulToken(), new QueryParams(watchTime, index));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[syncUnitGray] resp:{}", response);
}
// data not change
if (response.getConsulIndex() == null || index.equals(response.getConsulIndex())) {
return;
}
index = response.getConsulIndex();
// 推空保护,启动后理论上不存在单元化配置被删除的场景
if (response.getValue() != null) {
// 控制台 gzip 压缩加 base64 转换,这里进行解压
newContent = GzipUtil.base64DecodeDecompress(response.getValue().getDecodedValue());
loadUnitGray(newContent);
}
}
catch (Throwable t) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("[syncUnitGray] newContent:{} error:", newContent, t);
}
else {
LOGGER.warn("[syncUnitGray] error: {}", t.getMessage());
}
}
}
public static void loadUnitGray(String content) throws JsonProcessingException {
LOGGER.info("[unit] unit gray old raw content:\n{}", rawContent);
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TencentUnitManager.setUnitGray(mapper.readValue(content, UnitGray.class));
rawContent = content;
LOGGER.info("[unit] unit gray new raw content:\n{}", rawContent);
}
}

@ -0,0 +1,95 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.remote;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.tencent.cloud.common.util.GzipUtil;
import com.tencent.tsf.unit.core.Env;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.model.UnitRouteInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TencentUnitRouteRuleKVLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitRouteRuleKVLoader.class);
/**
* consul .
*/
private final static Integer watchTime = 55;
/**
* .
*/
private static Long index = -1L;
private static String rawContent;
private TencentUnitRouteRuleKVLoader() {
}
private static String getUnitRouteRuleKey() {
return "unit/routeRule/data";
}
public static void syncUnitRouteRule() {
String newContent = null;
try {
ConsulClient client = TsfUnitConsulManager.getConsulClient();
if (client == null) {
LOGGER.warn("[syncUnitRouteRule] tsf unit consul client is null");
return;
}
Response<GetValue> response = client.getKVValue(getUnitRouteRuleKey(),
Env.getConsulToken(), new QueryParams(watchTime, index));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[syncUnitRouteRule] resp:{}", response);
}
// data not change
if (response.getConsulIndex() == null || index.equals(response.getConsulIndex())) {
return;
}
index = response.getConsulIndex();
// 推空保护,启动后理论上不存在单元化配置被删除的场景
if (response.getValue() != null) {
// 控制台 gzip 压缩加 base64 转换,这里进行解压
newContent = GzipUtil.base64DecodeDecompress(response.getValue().getDecodedValue());
loadUnitRouteRule(newContent);
}
}
catch (Throwable t) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("[syncUnitRouteRule] newContent:{} error:", newContent, t);
}
else {
LOGGER.warn("[syncUnitRouteRule] error: {}", t.getMessage());
}
}
}
public static void loadUnitRouteRule(String content) throws JsonProcessingException {
LOGGER.info("[unit] unit route rule old raw content:\n{}", rawContent);
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TencentUnitManager.setUnitRouteRule(mapper.readValue(content, UnitRouteInfo.class));
rawContent = content;
LOGGER.info("[unit] unit route rule new raw content:\n{}", rawContent);
}
}

@ -0,0 +1,62 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.remote;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.ecwid.consul.v1.ConsulClient;
import com.tencent.polaris.api.utils.IPAddressUtils;
import com.tencent.tsf.unit.core.Env;
public final class TsfUnitConsulManager {
protected static final AtomicInteger POOL_SEQ = new AtomicInteger(1);
// 只需要 3 个长轮训
private final static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("tsf-unit-consul-" + POOL_SEQ.getAndIncrement());
t.setDaemon(true); // 设置为守护线程
return t;
}
});
private static ConsulClient consulClient = null;
private static volatile boolean init = false;
private TsfUnitConsulManager() {
}
public static synchronized void init() {
if (!init) {
init = true;
consulClient = new ConsulClient(IPAddressUtils.getIpCompatible(Env.getConsulHost()), Env.getConsulPort());
// 初始化先同步拉取一次
TencentUnitArchKVLoader.syncUnitArch();
TencentUnitRouteRuleKVLoader.syncUnitRouteRule();
TencentUnitGrayKVLoader.syncUnitGray();
// 启动长轮训定时任务
executorService.scheduleAtFixedRate(TencentUnitArchKVLoader::syncUnitArch, 55, 1, TimeUnit.SECONDS);
executorService.scheduleAtFixedRate(TencentUnitRouteRuleKVLoader::syncUnitRouteRule, 55, 1, TimeUnit.SECONDS);
executorService.scheduleAtFixedRate(TencentUnitGrayKVLoader::syncUnitGray, 55, 1, TimeUnit.SECONDS);
}
}
public static ConsulClient getConsulClient() {
return consulClient;
}
}

@ -0,0 +1,435 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.unit.core.TencentUnitContext;
import com.tencent.tsf.unit.core.TencentUnitContext.UnitCompositeContextMap;
import com.tencent.tsf.unit.core.TencentUnitManager;
import com.tencent.tsf.unit.core.UnitTag;
import com.tencent.tsf.unit.core.UnitTagConstant;
import com.tencent.tsf.unit.core.exception.ErrorCode;
import com.tencent.tsf.unit.core.exception.TencentUnitException;
import com.tencent.tsf.unit.core.model.RoutingUnit;
import com.tencent.tsf.unit.core.model.RoutingUnitContext;
import com.tencent.tsf.unit.core.model.TargetUnitInfo;
import com.tencent.tsf.unit.core.model.UnitArch;
import com.tencent.tsf.unit.core.model.UnitInfo;
import com.tencent.tsf.unit.core.model.UnitNamespace;
import com.tencent.tsf.unit.core.model.UnitRouteInfo;
import com.tencent.tsf.unit.core.model.UnitTagPosition;
import com.tencent.tsf.unit.core.model.UnitType;
import com.tencent.tsf.unit.core.remote.TsfUnitConsulManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 使使 TencentUnitManager.
*/
public final class TencentUnitUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitUtils.class);
private TencentUnitUtils() {
}
/**
* customerIdentifier.
*/
public static boolean checkLocalUnit(String customerIdentifier) {
RoutingUnit routingUnit = TencentUnitManager.getRoutingUnit(customerIdentifier);
if (routingUnit != null) {
return StringUtils.equals(routingUnit.getUnitId(), TencentUnitManager.getLocalUnitId());
}
else {
return false;
}
}
/**
* unitId cloud space .
*/
public static boolean checkLocalCloudSpace(String unitId) {
String targetCloudId = TencentUnitManager.getUnitCloudMap().get(unitId);
return StringUtils.equals(targetCloudId, TencentUnitManager.getLocalCloudSpaceId());
}
/**
* SDUGDU null.
*
* @return UnitInfo
*/
public static UnitInfo getLocalStandardUnitInfo() {
// 判断是否在 gdu 上
if (TencentUnitManager.getGduNsMap().containsKey(TencentUnitManager.getLocalUnitId())
|| TencentUnitManager.getGrayGduNsMap().containsKey(TencentUnitManager.getLocalUnitId())) {
return null;
}
return getStandardUnitInfo(TencentUnitManager.getLocalUnitId());
}
/**
* RegionGDU.
*
* @return
*/
public static UnitInfo getLocalGlobalUnitInfo() {
UnitInfo info = new UnitInfo();
UnitArch.CloudSpace cloudSpace = TencentUnitManager.getLocalCloudSpace();
if (cloudSpace != null) {
info.setRegionId(cloudSpace.getRegionId());
info.setRegionName(cloudSpace.getRegionName());
List<UnitArch.Gdu> gduList;
if (TencentUnitManager.inGrayUnit()) {
gduList = cloudSpace.getGrayGdus();
}
else {
gduList = cloudSpace.getGdus();
}
// 默认就是只有一个GDU
for (UnitArch.Gdu gdu : gduList) {
String failoverUnitId = TencentUnitManager.getFailoverUnitIdMap().get(gdu.getId());
// 如果有容灾单元,则使用容灾单元
if (StringUtils.isNotEmpty(failoverUnitId)
&& TencentUnitManager.getAllGduMap().containsKey(failoverUnitId)) {
gdu = TencentUnitManager.getAllGduMap().get(failoverUnitId);
}
info.setUnitId(gdu.getId());
info.setUnitType(UnitType.GDU.toString());
info.setZoneId(gdu.getZoneId());
info.setZoneName(gdu.getZoneName());
info.setAllNamespaceList(gdu.getNamespaces());
info.setAllShardingKeyList(Collections.emptyList());
return info;
}
}
return null;
}
/**
* SDU.
*
* @param systemName
* @param cid
* @return
*/
public static TargetUnitInfo getTargetStandardUnitInfo(String systemName, String cid) {
UnitInfo unitInfo = null;
TargetUnitInfo targetUnitInfo = null;
String customerNumber = null;
// 在灰度单元内
if (TencentUnitManager.inGrayUnit()) {
unitInfo = getStandardUnitInfo(TencentUnitManager.getGraySduUnitId());
targetUnitInfo = new TargetUnitInfo(unitInfo);
// 灰度不需要计算映射值
}
else {
RoutingUnit routingUnit = TencentUnitManager.getRoutingUnit(cid);
if (routingUnit == null) {
LOGGER.error("[unit] cid: {} not get any match unit route", cid);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, String.format("system: %s, cid: %s not found match unit route", systemName, cid));
}
customerNumber = routingUnit.getCustomerNumber();
unitInfo = getStandardUnitInfo(routingUnit.getUnitId());
targetUnitInfo = new TargetUnitInfo(unitInfo);
// 设置计算转换后的映射值
targetUnitInfo.setShardingKey(routingUnit.getShardingKey());
}
// 找到目标Namespace信息
for (UnitNamespace namespace : targetUnitInfo.getAllNamespaceList()) {
if (namespace.getBusinessSystemName().equals(systemName)) {
targetUnitInfo.setNamespaceId(namespace.getId());
targetUnitInfo.setNamespaceName(namespace.getName());
return targetUnitInfo;
}
}
LOGGER.error("[unit] cid: {}, customerNumber: {}, system: {}, not found target namespace",
cid, customerNumber, systemName);
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, String.format("system: %s, cid: %s not found target unit namespace", systemName, cid));
}
/**
* SDU.
* from 2025-01-17
*
* 使: 广SDU
*/
public static List<UnitInfo> getAllStandardUnitInfoList() {
// 当前是否处于灰度单元内
if (TencentUnitManager.inGrayUnit()) {
return getGraySduUnitInfos();
}
else {
return getSduUnitInfos();
}
}
/**
* .
*/
public static RoutingUnitContext getRoutingUnitContext() {
String customerNumber = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_TARGET_CUSTOMER_NUMBER);
String shardingKey = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_TARGET_SHARDING_KEY);
String unitId = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
return new RoutingUnitContext(customerNumber, shardingKey, unitId);
}
/**
* positionHEADERtags.
*/
public static void putGrayTags(String position, Map<String, String> tags) {
UnitTagPosition unitTagPosition = UnitTagPosition.fromString(position);
if (unitTagPosition == null) {
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "gray tag position format error, pos:" + position);
}
TencentUnitContext.clearGrayUserContext();
switch (unitTagPosition) {
case HEADER:
break;
default:
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "gray tag position only support header, pos:" + position);
}
TencentUnitContext.putGrayUserTags(position, tags);
}
public static void putCustomerTags(String system, String cid) {
TencentUnitContext.clearUserTags();
TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, system);
TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER, cid);
}
/**
* contextunit.
*
* @param system
* @param cid
* @param global GDU
*/
public static void putCustomerTags(String system, String cid, boolean global) {
putCustomerTags(system, cid);
if (global) {
TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
}
}
/**
* context+.
*/
public static void putTargetUnitTags(String system, String unitId) {
TencentUnitContext.clearUserTags();
TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, system);
TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID, unitId);
}
/**
* tsf spring cloud .
*/
public static void enable() {
TencentUnitManager.setEnable(true);
TsfUnitConsulManager.init();
}
public static UnitCompositeContextMap getCompositeContextMap() {
return TencentUnitContext.getCompositeContextMap();
}
public static void setUnitCompositeContextMap(UnitCompositeContextMap unitCompositeContextMap) {
TencentUnitContext.setUnitCompositeContextMap(unitCompositeContextMap);
}
public static List<Integer> getALLSharding() {
List<UnitRouteInfo.UnitRoute> unitRoutes = TencentUnitManager.getUnitRoutes();
if (unitRoutes.size() == 0) {
return Collections.emptyList();
}
List<Integer> shardingIdList = new ArrayList<>();
for (UnitRouteInfo.UnitRoute unitRoute : unitRoutes) {
appendShardingIdList(unitRoute, shardingIdList);
}
shardingIdList.sort(Comparator.naturalOrder());
return shardingIdList;
}
private static void appendShardingIdList(UnitRouteInfo.UnitRoute unitRoute, List<Integer> shardingIdList) {
// 单元化路径规则下仅支持识别操作符rangeequalin
List<UnitTag> unitTagList = unitRoute.getMatch().getConditions();
for (UnitTag unitTag : unitTagList) {
if (unitTag.getTagOperator().equals(UnitTagConstant.OPERATOR.RANGE)) {
String[] values = unitTag.getTagValue().split("\\s*,\\s*");
if (values.length != 2) {
LOGGER.warn("[unit] unit rule tag value: {} is invalid", unitTag.getTagValue());
continue;
}
// 转换为int进行range匹配
int start = Integer.parseInt(values[0]);
int end = Integer.parseInt(values[1]);
for (int i = start; i <= end; i++) {
shardingIdList.add(i);
}
}
else if (unitTag.getTagOperator().equals(UnitTagConstant.OPERATOR.EQUAL)) {
int target = Integer.parseInt(unitTag.getTagValue());
shardingIdList.add(target);
}
else if (unitTag.getTagOperator().equals(UnitTagConstant.OPERATOR.IN)) {
String[] values = unitTag.getTagValue().split("\\s*,\\s*");
for (String entry : values) {
int target = Integer.parseInt(entry);
shardingIdList.add(target);
}
}
}
}
/**
* SDU.
* private
*
* @param unitId
* @return UnitInfo
*/
private static UnitInfo getStandardUnitInfo(String unitId) {
if (StringUtils.isEmpty(unitId)) {
LOGGER.error("[unit] not found sdu local id");
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "unit id is empty");
}
UnitInfo info = new UnitInfo();
info.setUnitId(unitId);
// 填充单元信息包括sudsnamespaces等
fillStandardUnitInfo(info);
// 增加SharingList信息
appendStandardUnitShardingList(info);
return info;
}
private static void fillStandardUnitInfo(UnitInfo info) {
UnitArch.CloudSpace cloudSpace = TencentUnitManager.getLocalCloudSpace();
info.setRegionId(cloudSpace.getRegionId());
info.setRegionName(cloudSpace.getRegionName());
List<UnitArch.Sdu> sduList;
if (TencentUnitManager.inGrayUnit()) {
sduList = cloudSpace.getGraySdus();
}
else {
sduList = cloudSpace.getSdus();
}
for (UnitArch.Sdu sdu : sduList) {
if (!sdu.getId().equals(info.getUnitId())) {
continue;
}
info.setUnitType(UnitType.SDU.toString());
info.setZoneId(sdu.getZoneId());
info.setZoneName(sdu.getZoneName());
info.setAllNamespaceList(sdu.getNamespaces());
return;
}
LOGGER.error("[unit] unitId: {}, cloudSpace: {}, not found unit in sdu list",
info.getUnitId(), cloudSpace.getCloudId());
throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, String.format("local unitId: %s, cloudSpace: %s, not found in the sdu list",
info.getUnitId(), cloudSpace.getCloudId()));
}
/**
* shardingKeyList.
*/
private static void appendStandardUnitShardingList(UnitInfo info) {
List<UnitRouteInfo.UnitRoute> unitRoutes = TencentUnitManager.getUnitRoutes();
if (unitRoutes.size() == 0) {
LOGGER.warn("[unit] unit routes size is empty, so shardingList is empty");
return;
}
List<Integer> shardingIdList = new ArrayList<>();
for (UnitRouteInfo.UnitRoute unitRoute : unitRoutes) {
if (!unitRoute.getRoute().getActualUnitId().equals(info.getUnitId())) {
continue;
}
appendShardingIdList(unitRoute, shardingIdList);
}
// 容灾场景下会合并多个单元的 shardingKeyList, 这里需要排序
shardingIdList.sort(Comparator.naturalOrder());
info.setAllShardingKeyList(shardingIdList);
}
private static List<UnitInfo> getSduUnitInfos() {
List<UnitInfo> infos = new ArrayList<>();
for (UnitArch.CloudSpace cloudSpace : TencentUnitManager.getAllCloudSpaces()) {
if (cloudSpace.getSdus() == null) {
continue;
}
for (UnitArch.Sdu sdu : cloudSpace.getSdus()) {
// 过滤非活跃单元(故障单元、未配置路由单元)
if (!TencentUnitManager.getActiveSduIdMap().containsKey(sdu.getId())) {
continue;
}
infos.add(getUnitInfo(sdu, cloudSpace));
}
}
return infos;
}
private static List<UnitInfo> getGraySduUnitInfos() {
List<UnitInfo> infos = new ArrayList<>();
for (UnitArch.CloudSpace cloudSpace : TencentUnitManager.getAllCloudSpaces()) {
if (cloudSpace.getGraySdus() == null) {
continue;
}
for (UnitArch.Sdu sdu : cloudSpace.getGraySdus()) {
infos.add(getUnitInfo(sdu, cloudSpace));
}
}
return infos;
}
private static UnitInfo getUnitInfo(UnitArch.Sdu sdu, UnitArch.CloudSpace cloudSpace) {
UnitInfo info = new UnitInfo();
// 单元基础信息
info.setUnitId(sdu.getId());
info.setUnitType(UnitType.SDU.toString());
info.setZoneId(sdu.getZoneId());
info.setZoneName(sdu.getZoneName());
info.setAllNamespaceList(sdu.getNamespaces());
// 增加Region
info.setRegionId(cloudSpace.getRegionId());
info.setRegionName(cloudSpace.getRegionName());
// 增加SharingList信息
appendStandardUnitShardingList(info);
return info;
}
}

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 www.tencent.com.
* All Rights Reserved.
* This program is the confidential and proprietary information of
* www.tencent.com ("Confidential Information"). You shall not disclose such
* Confidential Information and shall use it only in accordance with
* the terms of the license agreement you entered into with www.tencent.com.
*/
package com.tencent.tsf.unit.core.zonefilter;
import java.util.HashSet;
import java.util.Set;
/**
* TSF .
*/
public final class TsfZoneFilterManager {
private TsfZoneFilterManager() {
}
private volatile static Set<String> disabledZoneSet = new HashSet<>();
public static Set<String> getDisabledZoneSet() {
return disabledZoneSet;
}
public static void setDisabledZoneSet(Set<String> disabledZoneSet) {
TsfZoneFilterManager.disabledZoneSet = disabledZoneSet;
}
}

@ -0,0 +1,25 @@
package com.tencent.cloud.plugin.unit.config;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
public class UnitAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(UnitAutoConfiguration.class))
.withPropertyValues(
"tsf_consul_ip=localhost"
);
@Test
void shouldCreateBeansWhenConditionsMet() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(UnitBeanPostProcessor.class);
});
}
}

@ -73,6 +73,7 @@ public class EnhancedServletFilter extends OncePerRequestFilter {
.url(URI.create(request.getRequestURL().toString()))
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());

@ -72,6 +72,11 @@ public class PluginOrderConstant {
*/
public static final int CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
/**
* order for ${@link com.tencent.cloud.plugin.unit.plugin.UnitRestTemplateEnhancedPlugin}.
*/
public static final int CONSUMER_UNIT_METADATA_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 11;
/**
* order for
* {@link com.tencent.cloud.plugin.trace.TraceMetadataEnhancedPlugin}.

Loading…
Cancel
Save