
com.tencent.polaris.plugins.router.rule.RuleBasedRouter Maven / Gradle / Ivy
/*
* Tencent is pleased to support the open source community by making Polaris available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.plugins.router.rule;
import com.tencent.polaris.api.config.consumer.ServiceRouterConfig;
import com.tencent.polaris.api.exception.ErrorCode;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.api.plugin.PluginType;
import com.tencent.polaris.api.plugin.common.InitContext;
import com.tencent.polaris.api.plugin.common.PluginTypes;
import com.tencent.polaris.api.plugin.route.RouteInfo;
import com.tencent.polaris.api.plugin.route.RouteResult;
import com.tencent.polaris.api.plugin.route.ServiceRouter;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceMetadata;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.MapUtils;
import com.tencent.polaris.api.utils.RuleUtils;
import com.tencent.polaris.client.pb.ModelProto.MatchString;
import com.tencent.polaris.client.pb.RoutingProto;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.plugins.router.common.AbstractServiceRouter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 基于规则的服务路由能力
*
* @author andrewshan
* @date 2019/8/28
*/
public class RuleBasedRouter extends AbstractServiceRouter {
private static final Logger LOG = LoggerFactory.getLogger(RuleBasedRouter.class);
private Map globalVariablesConfig;
/**
* 根据路由规则进行服务实例过滤, 并返回过滤后的实例列表
*
* @param routeInfo
* @param ruleMatchType
* @return 路由规则列表
* @throws PolarisException
*/
private List getRoutesFromRule(RouteInfo routeInfo, RuleMatchType ruleMatchType)
throws PolarisException {
// 跟据服务类型获取对应路由规则
// 被调inbound
if (ruleMatchType == RuleMatchType.destRouteRuleMatch) {
if (null == routeInfo.getDestRouteRule() || null == routeInfo.getDestRouteRule().getRule()) {
return null;
}
Object routing = routeInfo.getDestRouteRule().getRule();
if (!(routing instanceof RoutingProto.Routing)) {
throw new PolarisException(ErrorCode.API_INVALID_ARGUMENT, "getRuleFilteredInstances param invalid, "
+ "inbound routing must be instance of RoutingProto.Routing");
}
return ((RoutingProto.Routing) routing).getInboundsList();
}
// 主调outbound
if (null == routeInfo.getSourceRouteRule() || null == routeInfo.getSourceRouteRule().getRule()) {
return null;
}
Object routing = routeInfo.getSourceRouteRule().getRule();
if (!(routing instanceof RoutingProto.Routing)) {
throw new PolarisException(ErrorCode.API_INVALID_ARGUMENT, "getRuleFilteredInstances param invalid, "
+ "outbound routing must be instance of RoutingProto.Routing");
}
return ((RoutingProto.Routing) routing).getOutboundsList();
}
// 匹配source规则
private boolean matchSource(List sources, ServiceMetadata sourceService,
RuleMatchType ruleMatchType, Map multiEnvRouterParamMap) {
if (CollectionUtils.isEmpty(sources)) {
return true;
}
// source匹配成功标志
boolean matched = true;
for (RoutingProto.Source source : sources) {
// 对于inbound规则, 需要匹配source服务
if (ruleMatchType == RuleMatchType.destRouteRuleMatch) {
if (sourceService == null) {
// 如果没有source服务信息, 判断rule是否支持全匹配
if (!RuleUtils.MATCH_ALL.equals(source.getNamespace().getValue()) || !RuleUtils.MATCH_ALL
.equals(source.getService().getValue())) {
matched = false;
continue;
}
} else {
// 如果有source服务信息, 需要匹配服务信息
// 如果命名空间|服务不为"*"且不等于原服务, 则匹配失败
if (!RuleUtils.MATCH_ALL.equals(source.getNamespace().getValue()) && !source.getNamespace()
.getValue().equals(sourceService.getNamespace())) {
matched = false;
continue;
}
if (!RuleUtils.MATCH_ALL.equals(source.getService().getValue()) && !source.getService()
.getValue().equals(sourceService.getService())) {
matched = false;
continue;
}
}
}
// 如果rule中metadata为空, 匹配成功, 结束
if (MapUtils.isEmpty(source.getMetadataMap())) {
matched = true;
break;
}
// 如果没有源服务信息, 本次匹配失败
if (sourceService == null) {
matched = false;
continue;
}
matched = matchMetadata(
source.getMetadataMap(), sourceService.getMetadata(), true, multiEnvRouterParamMap);
if (matched) {
break;
}
}
return matched;
}
// 匹配metadata
private boolean matchMetadata(Map ruleMeta, Map destMeta,
boolean isMatchSource, Map multiEnvRouterParamMap) {
// 如果规则metadata为空, 返回成功
if (MapUtils.isEmpty(ruleMeta)) {
return true;
}
if (ruleMeta.containsKey(RuleUtils.MATCH_ALL)) {
return true;
}
// 如果规则metadata不为空, 待匹配规则为空, 直接返回失败
if (MapUtils.isEmpty(destMeta)) {
return false;
}
// metadata是否全部匹配
boolean allMetaMatched = true;
// dest中找到的metadata个数, 用于辅助判断是否能匹配成功
int matchNum = 0;
for (Map.Entry entry : ruleMeta.entrySet()) {
String ruleMetaKey = entry.getKey();
MatchString ruleMetaValue = entry.getValue();
if (RuleUtils.isMatchAllValue(ruleMetaValue)) {
matchNum++;
continue;
}
if (destMeta.containsKey(ruleMetaKey)) {
matchNum++;
if (!ruleMetaValue.hasValue()
&& ruleMetaValue.getValueType() != MatchString.ValueType.PARAMETER) {
continue;
}
String destMetaValue = destMeta.get(ruleMetaKey);
allMetaMatched = isAllMetaMatched(isMatchSource, true, ruleMetaKey, ruleMetaValue, destMetaValue,
multiEnvRouterParamMap);
}
if (!allMetaMatched) {
break;
}
}
// 如果一个metadata未找到, 匹配失败
if (matchNum == 0) {
allMetaMatched = false;
}
return allMetaMatched;
}
private boolean isAllMetaMatched(boolean isMatchSource, boolean allMetaMatched, String ruleMetaKey,
MatchString ruleMetaValue, String destMetaValue, Map multiEnvRouterParamMap) {
if (ruleMetaValue.getType() == MatchString.MatchStringType.REGEX) {
// 正则匹配
allMetaMatched = matchValueByValueType(isMatchSource, true, ruleMetaKey, ruleMetaValue, destMetaValue,
multiEnvRouterParamMap);
} else {
// 精确匹配
allMetaMatched = matchValueByValueType(isMatchSource, false, ruleMetaKey, ruleMetaValue, destMetaValue,
multiEnvRouterParamMap);
}
return allMetaMatched;
}
private boolean matchValueByValueType(boolean isMatchSource, boolean isRegex, String ruleMetaKey,
MatchString ruleMetaValue, String destMetaValue, Map multiEnvRouterParamMap) {
boolean allMetaMatched = true;
switch (ruleMetaValue.getValueType()) {
case PARAMETER:
// 通过参数传入
if (isMatchSource) {
// 当匹配的是source,记录请求的 K V
multiEnvRouterParamMap.put(ruleMetaKey, destMetaValue);
} else {
// 当匹配的是dest, 判断value
if (!multiEnvRouterParamMap.containsKey(ruleMetaKey)) {
allMetaMatched = false;
} else {
String ruleValue = multiEnvRouterParamMap.get(ruleMetaKey);
// contains key
allMetaMatched = matchValue(isRegex, destMetaValue, ruleValue);
}
}
break;
case VARIABLE:
if (globalVariablesConfig.containsKey(ruleMetaKey)) {
// 1.先从配置获取
String ruleValue = (String) globalVariablesConfig.get(ruleMetaKey);
allMetaMatched = matchValue(isRegex, destMetaValue, ruleValue);
} else {
// 2.从环境变量中获取 key从规则中获取
String key = ruleMetaValue.getValue().getValue();
if (!System.getenv().containsKey(key)) {
allMetaMatched = false;
} else {
String value = System.getenv(key);
allMetaMatched = matchValue(isRegex, destMetaValue, value);
}
if (!System.getenv().containsKey(key) || !System.getenv(key).equals(destMetaValue)) {
allMetaMatched = false;
}
}
break;
default:
allMetaMatched = matchValue(isRegex, destMetaValue, ruleMetaValue.getValue().getValue());
}
return allMetaMatched;
}
private boolean matchValue(boolean isRegex, String destMetaValue, String ruleValue) {
boolean allMetaMatched = true;
if (isRegex) {
boolean match = Utils.regMatch(ruleValue, destMetaValue);
if (!match) {
allMetaMatched = false;
}
} else {
if (!destMetaValue.equals(ruleValue)) {
allMetaMatched = false;
}
}
return allMetaMatched;
}
private List getRuleFilteredInstances(RouteInfo routeInfo, ServiceInstances instances,
RuleMatchType ruleMatchType) throws PolarisException {
// 获取路由规则
List routes = getRoutesFromRule(routeInfo, ruleMatchType);
if (CollectionUtils.isEmpty(routes)) {
return Collections.emptyList();
}
Map multiEnvRouterParamMap = new HashMap<>();
for (RoutingProto.Route route : routes) {
if (route == null) {
continue;
}
if (LOG.isDebugEnabled()) {
LOG.debug("getRuleFilteredInstances, route:{}", route.toString());
}
// 匹配source规则
boolean sourceMatched = matchSource(route.getSourcesList(), routeInfo.getSourceService(), ruleMatchType,
multiEnvRouterParamMap);
if (!sourceMatched) {
continue;
}
// 如果source匹配成功, 继续匹配destination规则
// 然后将结果写进map(key: 权重, value: 带权重的实例分组)
Map subsetsMap = new HashMap<>();
int smallestPriority = -1;
for (RoutingProto.Destination dest : route.getDestinationsList()) {
if (dest == null) {
continue;
}
// 对于outbound规则, 需要匹配DestService服务
if (ruleMatchType == RuleMatchType.sourceRouteRuleMatch) {
if (!RuleUtils.MATCH_ALL.equals(dest.getNamespace().getValue()) && !dest.getNamespace().getValue()
.equals(routeInfo.getDestService().getNamespace())) {
continue;
}
if (!RuleUtils.MATCH_ALL.equals(dest.getService().getValue()) && !dest.getService().getValue()
.equals(routeInfo.getDestService().getService())) {
continue;
}
}
if (dest.getWeight().getValue() == 0) {
continue;
}
boolean populated = populateSubsetsFromDest(instances, dest, subsetsMap, multiEnvRouterParamMap);
if (populated) {
int priority = dest.getPriority().getValue();
if (smallestPriority < 0 || smallestPriority > priority) {
smallestPriority = priority;
}
}
}
// 如果未匹配到分组, 继续匹配
if (MapUtils.isEmpty(subsetsMap)) {
continue;
}
// 匹配到分组, 返回
return selectInstances(routeInfo, subsetsMap.get(smallestPriority));
}
// 全部匹配完成, 未匹配到任何分组, 返回空
return Collections.emptyList();
}
/**
* populateSubsetsFromDest 根据destination中的规则填充分组列表
*
* @param instances 实例信息
* @param dest 目标规则
* @param subsetsMap 实例分组
* @return 是否成功加入subset列表
*/
private boolean populateSubsetsFromDest(
ServiceInstances instances, RoutingProto.Destination dest, Map subsetsMap,
Map multiEnvRouterParamMap) {
// 获取subset
List oriInstances = instances.getInstances();
List filteredInstances = new ArrayList<>();
for (Instance ins : oriInstances) {
if (!matchMetadata(dest.getMetadataMap(), ins.getMetadata(), false, multiEnvRouterParamMap)) {
continue;
}
filteredInstances.add(ins);
}
// subset中无实例
if (CollectionUtils.isEmpty(filteredInstances)) {
return false;
}
// 根据优先级填充subset列表
int priority = dest.getPriority().getValue();
int weight = dest.getWeight().getValue();
PrioritySubsets weightedSubsets = subsetsMap.get(priority);
if (weightedSubsets == null) {
PrioritySubsets prioritySubsets = new PrioritySubsets();
WeightedSubset weightedSubset = new WeightedSubset();
weightedSubset.setInstances(filteredInstances);
weightedSubset.setWeight(weight);
prioritySubsets.setSubsets(new ArrayList<>(Collections.singletonList(weightedSubset)));
prioritySubsets.setTotalWeight(weight);
subsetsMap.put(priority, prioritySubsets);
} else {
WeightedSubset weightedSubset = new WeightedSubset();
weightedSubset.setInstances(filteredInstances);
weightedSubset.setWeight(weight);
weightedSubsets.getSubsets().add(weightedSubset);
weightedSubsets.setTotalWeight(weightedSubsets.getTotalWeight() + weight);
}
return true;
}
//selectInstances 从subset中选取实例
private List selectInstances(RouteInfo routeInfo, PrioritySubsets weightedSubsets) {
if (weightedSubsets.getSubsets().size() == 1) {
return weightedSubsets.getSubsets().get(0).instances;
}
Random random = new Random();
// 根据区间算法选择subset
long weight = random.nextInt(weightedSubsets.getTotalWeight());
//匹配的区间下标
int matchedRange = -1;
for (WeightedSubset weightedSubset : weightedSubsets.getSubsets()) {
weight -= weightedSubset.getWeight();
if (weight < 0) {
return weightedSubset.getInstances();
}
}
return Collections.emptyList();
}
@Override
public RouteResult router(RouteInfo routeInfo, ServiceInstances instances) {
// 根据匹配过程修改状态, 默认无路由策略状态
RuleStatus ruleStatus;
// 优先匹配inbound规则, 成功则不需要继续匹配outbound规则
List destFilteredInstances = null;
List sourceFilteredInstances = null;
if (routeInfo.getDestRouteRule() != null) {
destFilteredInstances = getRuleFilteredInstances(routeInfo, instances,
RuleMatchType.destRouteRuleMatch);
if (destFilteredInstances.isEmpty()) {
ruleStatus = RuleStatus.destRuleFail;
} else {
ruleStatus = RuleStatus.destRuleSucc;
}
} else {
// 然后匹配outbound规则
sourceFilteredInstances = getRuleFilteredInstances(routeInfo, instances,
RuleMatchType.sourceRouteRuleMatch);
if (sourceFilteredInstances.isEmpty()) {
ruleStatus = RuleStatus.sourceRuleFail;
} else {
ruleStatus = RuleStatus.sourceRuleSucc;
}
}
switch (ruleStatus) {
case sourceRuleSucc:
return new RouteResult(sourceFilteredInstances, RouteResult.State.Next);
case destRuleSucc:
return new RouteResult(destFilteredInstances, RouteResult.State.Next);
default:
LOG.error("route rule not match, rule status: {}, not matched source {}", ruleStatus,
routeInfo.getSourceService());
return new RouteResult(Collections.emptyList(), RouteResult.State.Next);
}
}
private List getHealthyInstances(List instances) {
List healthyInstances = new ArrayList<>();
for (Instance instance : instances) {
if (instance.isHealthy()) {
healthyInstances.add(instance);
}
}
return healthyInstances;
}
private List getHealthyInstancesOrAllIfAllDead(List instances) {
List healthyInstances = getHealthyInstances(instances);
if (!healthyInstances.isEmpty()) {
//如果有健康实例,则只返回健康实例
LOG.info("getHealthyInstancesOrAllIfAllDead found {} healthy instances, return heathy ones",
healthyInstances.size());
return healthyInstances;
}
return instances;
}
private List getInstancesOfEnv(ServiceInstances serviceInstances, String env) {
List instances = serviceInstances.getInstances();
List retInstances = new ArrayList<>();
for (Instance ins : instances) {
if (env.equals(ins.getMetadata().get("env"))) {
retInstances.add(ins);
}
}
return retInstances;
}
@Override
public String getName() {
return ServiceRouterConfig.DEFAULT_ROUTER_RULE;
}
@Override
public PluginType getType() {
return PluginTypes.SERVICE_ROUTER.getBaseType();
}
@Override
public void init(InitContext ctx) throws PolarisException {
globalVariablesConfig = ctx.getConfig().getGlobal().getSystem().getVariables();
}
@Override
public ServiceRouter.Aspect getAspect() {
return ServiceRouter.Aspect.MIDDLE;
}
@Override
public boolean enable(RouteInfo routeInfo, ServiceMetadata dstSvcInfo) {
if (routeInfo.getSourceService() == null) {
return false;
}
List dstRoutes = getRoutesFromRule(routeInfo, RuleMatchType.destRouteRuleMatch);
List srcRoutes = getRoutesFromRule(routeInfo, RuleMatchType.sourceRouteRuleMatch);
return !(CollectionUtils.isEmpty(dstRoutes) && CollectionUtils.isEmpty(srcRoutes));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy