This is an automated email from the ASF dual-hosted git repository. liubao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git
commit 62607c2b604a53db8d5ce0ee79e2d8be3277a6bf Author: GuoYL <[email protected]> AuthorDate: Mon Nov 18 15:46:12 2019 +0800 [SCB-1407] Add gray release feature --- handlers/handler-router/pom.xml | 58 +++++++ .../apache/servicecomb/router/RouterFilter.java | 81 ++++++++++ .../servicecomb/router/cache/RouterRuleCache.java | 121 +++++++++++++++ .../router/constom/CanaryInvokeFilter.java | 95 ++++++++++++ .../router/constom/CanaryServerListFilter.java | 50 ++++++ .../router/constom/MicroserviceCache.java | 27 ++++ .../constom/ServiceCombCanaryDistributer.java | 18 +++ .../distribute/AbstractRouterDistributor.java | 170 +++++++++++++++++++++ .../router/distribute/RouterDistributor.java | 35 +++++ .../exception/RouterIllegalParamException.java | 16 ++ .../router/match/RouterHeaderFilterExt.java | 28 ++++ .../router/match/RouterRuleMatcher.java | 60 ++++++++ .../servicecomb/router/model/HeaderRule.java | 100 ++++++++++++ .../apache/servicecomb/router/model/Matcher.java | 100 ++++++++++++ .../servicecomb/router/model/PolicyRuleItem.java | 138 +++++++++++++++++ .../apache/servicecomb/router/model/RouteItem.java | 111 ++++++++++++++ .../servicecomb/router/model/ServiceInfoCache.java | 79 ++++++++++ .../apache/servicecomb/router/model/TagItem.java | 132 ++++++++++++++++ .../router/util/VersionCompareUtil.java | 38 +++++ handlers/pom.xml | 1 + 20 files changed, 1458 insertions(+) diff --git a/handlers/handler-router/pom.xml b/handlers/handler-router/pom.xml new file mode 100644 index 0000000..51fdc26 --- /dev/null +++ b/handlers/handler-router/pom.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>handlers</artifactId> + <groupId>org.apache.servicecomb</groupId> + <version>2.0.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>handler-router</artifactId> + + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> +<!-- <scope>test</scope>--> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> +<!-- <scope>test</scope>--> + </dependency> + <dependency> + <groupId>com.netflix.ribbon</groupId> + <artifactId>ribbon-loadbalancer</artifactId> + </dependency> + <dependency> + <groupId>com.netflix.hystrix</groupId> + <artifactId>hystrix-core</artifactId> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-core</artifactId> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-beans</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + </dependency> + <dependency> + <groupId>org.apache.servicecomb</groupId> + <artifactId>common-rest</artifactId> + </dependency> + <dependency> + <groupId>org.apache.servicecomb</groupId> + <artifactId>handler-loadbalance</artifactId> + </dependency> + </dependencies> +</project> \ No newline at end of file diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/RouterFilter.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/RouterFilter.java new file mode 100644 index 0000000..9cbd49f --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/RouterFilter.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router; + +import com.netflix.loadbalancer.Server; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.servicecomb.router.cache.RouterRuleCache; +import org.apache.servicecomb.router.distribute.RouterDistributor; +import org.apache.servicecomb.router.match.RouterRuleMatcher; +import org.apache.servicecomb.router.model.PolicyRuleItem; +import org.springframework.util.StringUtils; +import org.springframework.util.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * @Author GuoYl123 + * @Date 2019/10/16 + **/ +public class RouterFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(RouterFilter.class); + + public static <T extends Server, E> List<T> getFilteredListOfServers(List<T> list, + String targetServiceName, Map<String, String> headers, RouterDistributor<T, E> distributer) { + if (CollectionUtils.isEmpty(list)) { + return list; + } + if (StringUtils.isEmpty(targetServiceName)) { + return list; + } + if (headers == null) { + headers = new HashMap<>(); + } + LOGGER.debug("route management headers:{}", headers); + /** + * 1.初始化--进行cache缓存 + */ + if (!RouterRuleCache.doInit(targetServiceName)) { + LOGGER.debug("route management init failed"); + return list; + } + /** + * 2.match--拿到invoke相关信息 (header),匹配到唯一的rule + */ + PolicyRuleItem invokeRule = RouterRuleMatcher.getInstance().match(targetServiceName, headers); + + if (invokeRule == null) { + LOGGER.debug("route management match rule failed"); + return list; + } + + LOGGER.debug("route management match rule success: {}", invokeRule); + + /** + * 3.distribute--拿到server list选择endpoint进行流量分配 + */ + List<T> resultList = distributer.distribute(targetServiceName, list, invokeRule); + + LOGGER.debug("route management distribute rule success: {}", resultList); + + return resultList; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java new file mode 100644 index 0000000..ec438a5 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/cache/RouterRuleCache.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.cache; + +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import com.netflix.config.DynamicPropertyFactory; +import com.netflix.config.DynamicStringProperty; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.servicecomb.router.model.PolicyRuleItem; +import org.apache.servicecomb.router.model.ServiceInfoCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; +import org.yaml.snakeyaml.Yaml; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class RouterRuleCache { + + private static final Logger LOGGER = LoggerFactory.getLogger(RouterRuleCache.class); + + private static ConcurrentHashMap<String, ServiceInfoCache> serviceInfoCacheMap = new ConcurrentHashMap<>(); + + private static final String ROUTE_RULE = "servicecomb.routeRule.%s"; + + private static Interner<String> servicePool = Interners.newWeakInterner(); + + /** + * 每次序列化额外缓存,配置更新时触发回调函数 返回false即初始化规则失败: 1. 规则解析错误 2. 规则为空 + * + * @param targetServiceName + * @return + */ + public static boolean doInit(String targetServiceName) { + if (serviceInfoCacheMap.containsKey(targetServiceName)) { + return true; + } + //这里使用guava包装String.intern():因为String.intern()分配在Old Generation,容易引发fullgc + synchronized (servicePool.intern(targetServiceName)) { + //Yaml not thread-safe + Yaml yaml = new Yaml(); + DynamicStringProperty ruleStr = DynamicPropertyFactory.getInstance().getStringProperty( + String.format(ROUTE_RULE, targetServiceName), null, () -> { + refresh(targetServiceName); + DynamicStringProperty tepRuleStr = DynamicPropertyFactory.getInstance() + .getStringProperty(String.format(ROUTE_RULE, targetServiceName), null); + if (tepRuleStr.get() == null) { + return; + } + try { + List<PolicyRuleItem> temList = Arrays + .asList(yaml.loadAs(tepRuleStr.get(), PolicyRuleItem[].class)); + RouterRuleCache.addAllRule(targetServiceName, temList); + } catch (Exception e) { + LOGGER.error("route management Serialization failed {}", e.getMessage()); + return; + } + }); + if (ruleStr.get() == null) { + return false; + } + try { + addAllRule(targetServiceName, + Arrays.asList(yaml.loadAs(ruleStr.get(), PolicyRuleItem[].class))); + } catch (Exception e) { + LOGGER.error("route management Serialization failed: {}", e.getMessage()); + return false; + } + return true; + } + } + + private static void addAllRule(String targetServiceName, + List<PolicyRuleItem> policyRuleItemList) { + if (CollectionUtils.isEmpty(policyRuleItemList)) { + return; + } + if (serviceInfoCacheMap.get(targetServiceName) == null) { + serviceInfoCacheMap.put(targetServiceName, new ServiceInfoCache()); + } + serviceInfoCacheMap.get(targetServiceName).setAllrule(policyRuleItemList); + // 这里初始化tagitem + serviceInfoCacheMap.get(targetServiceName).getAllrule().forEach(a -> + a.getRoute().forEach(b -> b.initTagItem()) + ); + // 按照优先级排序 + serviceInfoCacheMap.get(targetServiceName).sortRule(); + } + + public static ConcurrentHashMap<String, ServiceInfoCache> getServiceInfoCacheMap() { + return serviceInfoCacheMap; + } + + public static void refresh() { + serviceInfoCacheMap = new ConcurrentHashMap<>(); + servicePool = Interners.newWeakInterner(); + } + + public static void refresh(String targetServiceName) { + serviceInfoCacheMap.remove(targetServiceName); + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/CanaryInvokeFilter.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/CanaryInvokeFilter.java new file mode 100644 index 0000000..b41b216 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/CanaryInvokeFilter.java @@ -0,0 +1,95 @@ +package org.apache.servicecomb.router.constom; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import org.apache.servicecomb.common.rest.filter.HttpServerFilter; +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.core.definition.OperationMeta; +import org.apache.servicecomb.foundation.vertx.http.HttpServletRequestEx; +import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx; +import org.apache.servicecomb.swagger.invocation.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.yaml.snakeyaml.Yaml; + +import com.netflix.config.DynamicPropertyFactory; + +import io.vertx.core.json.Json; + +public class CanaryInvokeFilter implements HttpServerFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(CanaryInvokeFilter.class); + + private static final String PASS_HEADER = "servicecomb.passheader"; + + @Override + public int getOrder() { + return -90; + } + + @Override + public boolean enabled() { + return false; + } + + @Override + public boolean needCacheRequest(OperationMeta operationMeta) { + return false; + } + + /** + * 透传Header需要在这里实现, 因为无法预知调用链上的服务匹配所需要header, 提供两种模式 1.取到全量的header并放到context中, 2.从配置中解析并读取 + * + * @param invocation + * @param httpServletRequestEx + * @return + */ + @Override + public Response afterReceiveRequest(Invocation invocation, + HttpServletRequestEx httpServletRequestEx) { + if (invocation.getContext("canary_context") != null) { + Map<String, String> headerMap = getHeaderMap(invocation.getMicroserviceName(), + httpServletRequestEx); + invocation.addContext("canary_context", Json.encode(headerMap)); + } + return null; + } + + /** + * 取出所用的header + * + * @param serviceName + * @param httpServletRequestEx + * @return + */ + public Map<String, String> getHeaderMap(String serviceName, + HttpServletRequestEx httpServletRequestEx) { + Yaml yaml = new Yaml(); + String headerStr = DynamicPropertyFactory.getInstance().getStringProperty(PASS_HEADER, null) + .get(); + Map<String, String> headerKeyMap = yaml.load(headerStr); + Set<String> headerKeySet = headerKeyMap.keySet(); + Map<String, String> headerMap = new HashMap<>(); + headerKeySet.forEach(headerKey -> { + String val = httpServletRequestEx.getHeader(headerKey); + if (!StringUtils.isEmpty(val)) { + headerMap.put(headerKey, httpServletRequestEx.getHeader(headerKey)); + } + }); + return headerMap; + } + + @Override + public CompletableFuture<Void> beforeSendResponseAsync(Invocation invocation, + HttpServletResponseEx responseEx) { + return null; + } + + @Override + public void beforeSendResponse(Invocation invocation, HttpServletResponseEx responseEx) { + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/CanaryServerListFilter.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/CanaryServerListFilter.java new file mode 100644 index 0000000..dc9211a --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/CanaryServerListFilter.java @@ -0,0 +1,50 @@ +package org.apache.servicecomb.router.constom; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.servicecomb.core.Invocation; +import org.apache.servicecomb.loadbalance.ServerListFilterExt; +import org.apache.servicecomb.loadbalance.ServiceCombServer; +import com.netflix.config.DynamicPropertyFactory; + +import io.vertx.core.json.Json; +import org.apache.servicecomb.router.RouterFilter; +import org.apache.servicecomb.router.distribute.RouterDistributor; + +public class CanaryServerListFilter implements ServerListFilterExt { + + private static final String ENABLE = "servicecomb.release_way"; + + RouterDistributor distributer = new ServiceCombCanaryDistributer(); + + @Override + public boolean enabled() { + return DynamicPropertyFactory.getInstance().getStringProperty(ENABLE, "").get() + .equals("canary"); + } + + @Override + public List<ServiceCombServer> getFilteredListOfServers(List<ServiceCombServer> list, + Invocation invocation) { + String targetServiceName = invocation.getMicroserviceName(); + Map<String, String> headers = new HashMap<>(); + if (invocation.getContext("canary_context") != null) { + Map<String, String> canaryContext = Json + .decodeValue(invocation.getContext("canary_context"), Map.class); + headers.putAll(canaryContext); + } + for (int i = 0; i < invocation.getArgs().length; i++) { + if (invocation.getOperationMeta().getParamName(i) != null && + invocation.getArgs()[i] != null) { + headers + .put(invocation.getOperationMeta().getParamName(i), invocation.getArgs()[i].toString()); + } + } + headers.putAll(invocation.getContext()); + return RouterFilter + .getFilteredListOfServers(list, targetServiceName, headers, + distributer); + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/MicroserviceCache.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/MicroserviceCache.java new file mode 100644 index 0000000..a825d35 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/MicroserviceCache.java @@ -0,0 +1,27 @@ +package org.apache.servicecomb.router.constom; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.servicecomb.serviceregistry.RegistryUtils; +import org.apache.servicecomb.serviceregistry.api.registry.Microservice; + +public final class MicroserviceCache { + + private static MicroserviceCache instance = new MicroserviceCache(); + private Map<String, Microservice> services = new HashMap<>(); + + private MicroserviceCache() { + } + + public static MicroserviceCache getInstance() { + return instance; + } + + public Microservice getService(String serviceId) { + Microservice micorservice = services.computeIfAbsent(serviceId, (k) -> { + return RegistryUtils.getMicroservice(serviceId); + }); + return micorservice; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/ServiceCombCanaryDistributer.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/ServiceCombCanaryDistributer.java new file mode 100644 index 0000000..aaabc93 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/constom/ServiceCombCanaryDistributer.java @@ -0,0 +1,18 @@ +package org.apache.servicecomb.router.constom; + +import org.apache.servicecomb.loadbalance.ServiceCombServer; +import org.apache.servicecomb.router.distribute.AbstractRouterDistributor; +import org.apache.servicecomb.serviceregistry.api.registry.Microservice; + +public class ServiceCombCanaryDistributer extends + AbstractRouterDistributor<ServiceCombServer, Microservice> { + + public ServiceCombCanaryDistributer() { + init(server -> MicroserviceCache.getInstance() + .getService(server.getInstance().getServiceId()), + Microservice::getVersion, + Microservice::getServiceName, + Microservice::getProperties); + } + +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java new file mode 100644 index 0000000..83e1cc9 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/distribute/AbstractRouterDistributor.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.distribute; + +import com.netflix.loadbalancer.Server; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.servicecomb.router.cache.RouterRuleCache; +import org.apache.servicecomb.router.model.PolicyRuleItem; +import org.apache.servicecomb.router.model.RouteItem; +import org.apache.servicecomb.router.model.TagItem; +import org.apache.servicecomb.router.util.VersionCompareUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public abstract class AbstractRouterDistributor<T extends Server, E> implements + RouterDistributor<T, E> { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRouterDistributor.class); + + private Function<T, E> getIns; + + private Function<E, String> getVersion; + + private Function<E, String> getServerName; + + private Function<E, Map<String, String>> getProperties; + + @Override + public List<T> distribute(String targetServiceName, List<T> list, PolicyRuleItem invokeRule) { + //初始化LatestVersion + initLatestVersion(targetServiceName, list); + + invokeRule.check( + RouterRuleCache.getServiceInfoCacheMap().get(targetServiceName).getLatestVersionTag()); + + // 建立tag list + Map<TagItem, List<T>> versionServerMap = getDistributList(targetServiceName, list, invokeRule); + + //如果没有匹配到合适的规则,直接返回最新版本的服务列表 + if (CollectionUtils.isEmpty(versionServerMap)) { + LOGGER.debug("route management can not match any rule and route the latest version"); + return getLatestVersionList(list, targetServiceName); + } + + // 分配流量,返回结果 + TagItem targetTag = getFiltedServerTagItem(invokeRule, targetServiceName); + if (versionServerMap.containsKey(targetTag)) { + return versionServerMap.get(targetTag); + } + return getLatestVersionList(list, targetServiceName); + } + + @Override + public void init(Function<T, E> getIns, + Function<E, String> getVersion, + Function<E, String> getServerName, + Function<E, Map<String, String>> getProperties) { + this.getIns = getIns; + this.getVersion = getVersion; + this.getServerName = getServerName; + this.getProperties = getProperties; + } + + public TagItem getFiltedServerTagItem(PolicyRuleItem rule, String targetServiceName) { + return RouterRuleCache.getServiceInfoCacheMap().get(targetServiceName) + .getNextInvokeVersion(rule); + } + + /** + * 1.过滤targetService 2.返回按照version和tags分配list 这里之所以需要建立Map,而不是直接遍历List来分配是因为需要考虑 “多重匹配” + * 因为getProperties中除了tag还有其他的无关字段 + * + * @param serviceName + * @param list + * @return + */ + private Map<TagItem, List<T>> getDistributList(String serviceName, + List<T> list, + PolicyRuleItem invokeRule) { + String latestV = RouterRuleCache.getServiceInfoCacheMap().get(serviceName).getLatestVersionTag() + .getVersion(); + Map<TagItem, List<T>> versionServerMap = new HashMap<>(); + for (T server : list) { + //获得目标服务 + E ms = getIns.apply(server); + if (getServerName.apply(ms).equals(serviceName)) { + //最多匹配原则 + TagItem tagItem = new TagItem(getVersion.apply(ms), getProperties.apply(ms)); + TagItem targetTag = null; + int maxMatch = 0; + for (RouteItem entry : invokeRule.getRoute()) { + int nowMatch = entry.getTagitem().matchNum(tagItem); + if (nowMatch > maxMatch) { + maxMatch = nowMatch; + targetTag = entry.getTagitem(); + } + } + synchronized (invokeRule) { + if (invokeRule.isWeightLess() && getVersion.apply(ms).equals(latestV)) { + TagItem latestVTag = invokeRule.getRoute().get(invokeRule.getRoute().size() - 1) + .getTagitem(); + if (!versionServerMap.containsKey(latestVTag)) { + versionServerMap.put(latestVTag, new ArrayList<>()); + } + versionServerMap.get(latestVTag).add(server); + } + if (targetTag != null) { + if (!versionServerMap.containsKey(targetTag)) { + versionServerMap.put(targetTag, new ArrayList<>()); + } + versionServerMap.get(targetTag).add(server); + } + } + } + } + return versionServerMap; + } + + + public void initLatestVersion(String serviceName, List<T> list) { + if (RouterRuleCache.getServiceInfoCacheMap().get(serviceName).getLatestVersionTag() != null) { + return; + } + String latestVersion = null; + for (T server : list) { + E ms = getIns.apply(server); + if (getServerName.apply(ms).equals(serviceName)) { + if (latestVersion == null || VersionCompareUtil + .compareVersion(latestVersion, getVersion.apply(ms)) == -1) { + latestVersion = getVersion.apply(ms); + } + } + } + TagItem tagitem = new TagItem(latestVersion); + RouterRuleCache.getServiceInfoCacheMap().get(serviceName).setLatestVersionTag(tagitem); + } + + + public List<T> getLatestVersionList(List<T> list, String targetServiceName) { + String latestV = RouterRuleCache.getServiceInfoCacheMap().get(targetServiceName) + .getLatestVersionTag().getVersion(); + return list.stream().filter(server -> + getVersion.apply(getIns.apply(server)).equals(latestV) + ).collect(Collectors.toList()); + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java new file mode 100644 index 0000000..fe5cf37 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/distribute/RouterDistributor.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.distribute; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.apache.servicecomb.router.model.PolicyRuleItem; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public interface RouterDistributor<T, E> { + + void init(Function<T, E> getIns, Function<E, String> getVersion, + Function<E, String> getServerName, + Function<E, Map<String, String>> getProperties); + + List<T> distribute(String targetServiceName, List<T> list, PolicyRuleItem invokeRule); +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/exception/RouterIllegalParamException.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/exception/RouterIllegalParamException.java new file mode 100644 index 0000000..e8046b6 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/exception/RouterIllegalParamException.java @@ -0,0 +1,16 @@ +package org.apache.servicecomb.router.exception; + +/** + * @Author GuoYl123 + * @Date 2019/11/4 + **/ +public class RouterIllegalParamException extends RuntimeException { + + public RouterIllegalParamException(String message) { + super(message); + } + + public RouterIllegalParamException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/match/RouterHeaderFilterExt.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/match/RouterHeaderFilterExt.java new file mode 100644 index 0000000..62e5161 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/match/RouterHeaderFilterExt.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.match; + +import java.util.Map; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public interface RouterHeaderFilterExt { + + Map<String, String> doFilter(Map<String, String> invokeHeader); +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/match/RouterRuleMatcher.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/match/RouterRuleMatcher.java new file mode 100644 index 0000000..5133eb0 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/match/RouterRuleMatcher.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.match; + +import java.util.Map; +import org.apache.servicecomb.router.cache.RouterRuleCache; +import org.apache.servicecomb.router.model.PolicyRuleItem; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class RouterRuleMatcher { + + @Autowired(required = false) + private RouterHeaderFilterExt routerHeaderFilterExt; + + private static RouterRuleMatcher instance = new RouterRuleMatcher(); + + private RouterRuleMatcher() { + } + + /** + * 匹配到合适的rule 匹配规则即: source (目标服务名字) sourceTags (一期先不考虑) headers (匹配header字段) + * + * @param serviceName + * @return + */ + public PolicyRuleItem match(String serviceName, Map<String, String> invokeHeader) { + if (routerHeaderFilterExt != null) { + invokeHeader = routerHeaderFilterExt.doFilter(invokeHeader); + } + for (PolicyRuleItem rule : RouterRuleCache.getServiceInfoCacheMap().get(serviceName) + .getAllrule()) { + if (rule.getMatch() == null || rule.getMatch().match(invokeHeader)) { + return rule; + } + } + return null; + } + + public static RouterRuleMatcher getInstance() { + return instance; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/HeaderRule.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/HeaderRule.java new file mode 100644 index 0000000..332b9f2 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/HeaderRule.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.model; + +import org.apache.servicecomb.router.exception.RouterIllegalParamException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class HeaderRule { + + private static final Logger LOGGER = LoggerFactory.getLogger(HeaderRule.class); + + //正则 + private String regex; + //是否区分大小写 false区分 true不区分 + private Boolean caseInsensitive = false; + //精准匹配 + private String exact; + + public HeaderRule() { + } + + public boolean match(String str) { + if (str == null) { + return false; + } + if (exact == null && regex == null) { + throw new RouterIllegalParamException( + "route management regex and exact can not br null at same time."); + } + if (!caseInsensitive) { + str = str.toLowerCase(); + exact = exact == null ? null : exact.toLowerCase(); + regex = regex == null ? null : regex.toLowerCase(); + } + if (exact != null && !str.equals(exact)) { + return false; + } + try { + if (regex != null && !str.matches(regex)) { + return false; + } + } catch (Exception e) { + LOGGER.error("route management wrong regular expression format: {}", regex); + return false; + } + return true; + } + + public String getRegex() { + return regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } + + public Boolean getCaseInsensitive() { + return caseInsensitive; + } + + public void setCaseInsensitive(Boolean caseInsensitive) { + this.caseInsensitive = caseInsensitive; + } + + public String getExact() { + return exact; + } + + public void setExact(String exact) { + this.exact = exact; + } + + @Override + public String toString() { + return "HeaderRule{" + + "regex='" + regex + '\'' + + ", caseInsensitive=" + caseInsensitive + + ", exact='" + exact + '\'' + + '}'; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/Matcher.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/Matcher.java new file mode 100644 index 0000000..83f5b66 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/Matcher.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.model; + +import java.util.Map; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class Matcher { + + //服务级别 + private String source; + //服务级别 -- 这个暂时不考虑 + private Map<String, String> sourceTags; + //invoke级别 + private Map<String, HeaderRule> headers; + //这个暂时不考虑 + private String refer; + + public Matcher() { + } + + public boolean filte(String sourcName, Map<String, String> sourceTags) { + if (sourcName != null && !sourcName.equals(source)) { + return false; + } + return true; + } + + public boolean match(Map<String, String> realHeaders) { + if (headers == null) { + return true; + } + for (Map.Entry<String, HeaderRule> entry : headers.entrySet()) { + if (!realHeaders.containsKey(entry.getKey()) || !entry.getValue() + .match(realHeaders.get(entry.getKey()))) { + return false; + } + } + return true; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Map<String, String> getSourceTags() { + return sourceTags; + } + + public void setSourceTags(Map<String, String> sourceTags) { + this.sourceTags = sourceTags; + } + + public Map<String, HeaderRule> getHeaders() { + return headers; + } + + public void setHeaders(Map<String, HeaderRule> headers) { + this.headers = headers; + } + + public String getRefer() { + return refer; + } + + public void setRefer(String refer) { + this.refer = refer; + } + + @Override + public String toString() { + return "Matcher{" + + "source='" + source + '\'' + + ", sourceTags=" + sourceTags + + ", headers=" + headers + + ", refer='" + refer + '\'' + + '}'; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java new file mode 100644 index 0000000..aee0c9a --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/PolicyRuleItem.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.model; + +import java.util.List; +import org.apache.servicecomb.router.exception.RouterIllegalParamException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class PolicyRuleItem implements Comparable<PolicyRuleItem> { + + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyRuleItem.class); + + private Integer precedence; + + private Matcher match; + + // any match 只要version符合就算符合匹配规则 + private List<RouteItem> route; + + private Integer total; + + private boolean weightLess = false; + + public PolicyRuleItem() { + } + + /** + * 如果weight和小于100,用latestVersion补充 + * + * @param latestVersionTag + */ + public void check(TagItem latestVersionTag) { + if (CollectionUtils.isEmpty(route)) { + throw new RouterIllegalParamException("canary rule list can not be null"); + } + if (route.size() == 1) { + route.get(0).setWeight(100); + return; + } + int sum = 0; + for (RouteItem item : route) { + if (item.getWeight() == null) { + throw new RouterIllegalParamException("canary rule weight can not be null"); + } + sum += item.getWeight(); + } + if (sum > 100) { + LOGGER.warn("canary rule weight sum is more than 100"); + } else if (sum < 100) { + if (latestVersionTag == null) { + LOGGER.warn("canary has some error when set default latestVersion"); + } + weightLess = true; + route.add(new RouteItem(100 - sum, latestVersionTag)); + } + //Collections.sort(route); + } + + @Override + public int compareTo(PolicyRuleItem param) { + if (param.precedence.equals(this.precedence)) { + LOGGER.warn("the same canary precedence is not recommended"); + return 0; + } + return param.precedence > this.precedence ? 1 : -1; + } + + public Integer getPrecedence() { + return precedence; + } + + public void setPrecedence(Integer precedence) { + this.precedence = precedence; + } + + public Matcher getMatch() { + return match; + } + + public void setMatch(Matcher match) { + this.match = match; + } + + public List<RouteItem> getRoute() { + return route; + } + + public void setRoute(List<RouteItem> route) { + this.route = route; + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + + public boolean isWeightLess() { + return weightLess; + } + + public void setWeightLess(boolean weightLess) { + this.weightLess = weightLess; + } + + @Override + public String toString() { + return "PolicyRuleItem{" + + "precedence=" + precedence + + ", match=" + match + + ", route=" + route + + ", total=" + total + + ", weightLess=" + weightLess + + '}'; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/RouteItem.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/RouteItem.java new file mode 100644 index 0000000..6afa2b3 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/RouteItem.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.model; + +import java.util.Map; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class RouteItem implements Comparable<RouteItem> { + + private Integer weight; + /** + * 负载均衡参数 + */ + private Integer currentWeight = 0; + /** + * 为了提高序列化速度设置为Map 固定字段 version + */ + private Map<String, String> tags; + + private TagItem tagitem; + + + public void initTagItem() { + if (tags != null && tags.containsKey("version")) { + tagitem = new TagItem(tags); + } + } + + public void addCurrentWeight() { + currentWeight += weight; + } + + public void reduceCurrentWeight(int total) { + currentWeight -= total; + } + + public RouteItem() { + } + + public RouteItem(Integer weight, TagItem tags) { + this.weight = weight; + this.tagitem = tags; + } + + public Integer getWeight() { + return weight; + } + + public void setWeight(Integer weight) { + this.weight = weight; + } + + public Integer getCurrentWeight() { + return currentWeight; + } + + public void setCurrentWeight(Integer currentWeight) { + this.currentWeight = currentWeight; + } + + public Map<String, String> getTags() { + return tags; + } + + public void setTags(Map<String, String> tags) { + this.tags = tags; + } + + public TagItem getTagitem() { + return tagitem; + } + + public void setTagitem(TagItem tagitem) { + this.tagitem = tagitem; + } + + @Override + public int compareTo(RouteItem param) { + if (param.weight == this.weight) { + return 0; + } + return param.weight > this.weight ? 1 : -1; + } + + @Override + public String toString() { + return "RouteItem{" + + "weight=" + weight + + ", currentWeight=" + currentWeight + + ", tags=" + tags + + ", tagitem=" + tagitem + + '}'; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java new file mode 100644 index 0000000..903ef45 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/ServiceInfoCache.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.model; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class ServiceInfoCache { + + private List<PolicyRuleItem> allrule; + //用于default的情况 + private TagItem latestVersionTag; + + public ServiceInfoCache() { + } + + public void sortRule() { + allrule = allrule.stream().sorted().collect(Collectors.toList()); + } + + public TagItem getNextInvokeVersion(PolicyRuleItem policyRuleItem) { + List<RouteItem> rule = policyRuleItem.getRoute(); + if (policyRuleItem.getTotal() == null) { + policyRuleItem.setTotal(rule.stream().mapToInt(RouteItem::getWeight).sum()); + } + rule.stream().forEach(RouteItem::addCurrentWeight); + int maxIndex = 0, maxWeight = -1; + for (int i = 0; i < rule.size(); i++) { + if (maxWeight < rule.get(i).getCurrentWeight()) { + maxIndex = i; + maxWeight = rule.get(i).getCurrentWeight(); + } + } + rule.get(maxIndex).reduceCurrentWeight(policyRuleItem.getTotal()); + return rule.get(maxIndex).getTagitem(); + } + + public List<PolicyRuleItem> getAllrule() { + return allrule; + } + + public void setAllrule(List<PolicyRuleItem> allrule) { + this.allrule = allrule; + } + + public TagItem getLatestVersionTag() { + return latestVersionTag; + } + + public void setLatestVersionTag(TagItem latestVersionTag) { + this.latestVersionTag = latestVersionTag; + } + + @Override + public String toString() { + return "ServiceInfoCache{" + + "allrule=" + allrule + + ", latestVersionTag=" + latestVersionTag + + '}'; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/TagItem.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/TagItem.java new file mode 100644 index 0000000..3f3230a --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/model/TagItem.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.apache.servicecomb.router.model; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @Author GuoYl123 + * @Date 2019/10/17 + **/ +public class TagItem { + + private String version; + private Map<String, String> param; + + public TagItem() { + } + + public TagItem(String version, Map<String, String> param) { + this.version = version; + this.param = param; + } + + public TagItem(String version) { + this.version = version; + Map<String, String> param = new HashMap<>(); + param.put("version", version); + this.param = param; + } + + public TagItem(Map<String, String> param) { + if (param.containsKey("version")) { + this.version = param.get("version"); + } + this.param = param; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public Map<String, String> getParam() { + return param; + } + + public void setParam(Map<String, String> param) { + this.param = param; + } + + /** + * map在匹配key调用 + * + * @return + */ + @Override + public int hashCode() { + int result = Objects.hash(version); + if (param != null) { + result = 31 * result + param.hashCode(); + } + return result; + } + + /** + * all match map在匹配key调用 + * + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj != null && !(obj instanceof TagItem)) { + return false; + } + TagItem item = (TagItem) obj; + if (this.param == null && item.getParam() == null) { + return this.version.equals(item.getVersion()); + } + if (this.param == null || item.getParam() == null) { + return false; + } + if (this.version != null && item.getVersion() != null) { + return this.version.equals(item.getVersion()) && this.param.equals(item.getParam()); + } + return this.param.equals(item.getParam()); + } + + + /** + * 返回匹配的个数 + * + * @param item + * @return + */ + public int matchNum(TagItem item) { + int cnt = 0; + if (version != null && !version.equals(item.version)) { + return 0; + } + for (Map.Entry<String, String> entry : param.entrySet()) { + if (item.getParam().containsKey(entry.getKey()) && + !item.getParam().get(entry.getKey()).equals(entry.getValue())) { + return 0; + } + cnt++; + } + return cnt; + } +} diff --git a/handlers/handler-router/src/main/java/org/apache/servicecomb/router/util/VersionCompareUtil.java b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/util/VersionCompareUtil.java new file mode 100644 index 0000000..e524973 --- /dev/null +++ b/handlers/handler-router/src/main/java/org/apache/servicecomb/router/util/VersionCompareUtil.java @@ -0,0 +1,38 @@ +package org.apache.servicecomb.router.util; + +/** + * @Author GuoYl123 + * @Date 2019/10/16 + **/ +public class VersionCompareUtil { + /** + * 前者大则返回一个正数 + * + * @param version1 + * @param version2 + * @return + */ + public static int compareVersion(String version1, String version2) { + if (version1 == null || version2 == null) { + throw new RuntimeException("version can not be null"); + } + String[] versionArray1 = version1.split("\\."); + String[] versionArray2 = version2.split("\\."); + int idx = 0; + int diff = 0; + int minLength = Math.min(versionArray1.length, versionArray2.length); + while (idx < minLength + && versionArray1[idx].length() - versionArray2[idx].length() == 0 + && versionArray1[idx].compareTo(versionArray2[idx]) == 0) { + ++idx; + } + idx = idx < minLength ? idx : idx - 1; + if (versionArray1[idx].length() - versionArray2[idx].length() == 0) { + diff = versionArray1[idx].compareTo(versionArray2[idx]); + } else { + diff = versionArray1[idx].length() - versionArray2[idx].length(); + } + diff = (diff != 0) ? diff : versionArray1.length - versionArray2.length; + return diff; + } +} diff --git a/handlers/pom.xml b/handlers/pom.xml index 3672dfe..999fcb1 100644 --- a/handlers/pom.xml +++ b/handlers/pom.xml @@ -38,6 +38,7 @@ <module>handler-loadbalance</module> <module>handler-fault-injection</module> <module>handler-publickey-auth</module> + <module>handler-router</module> </modules> </project>
