All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.polaris.plugins.router.lane.LaneRouter Maven / Gradle / Ivy

There is a newer version: 2.0.1.0-RC1
Show newest version
/*
 * Tencent is pleased to support the open source community by making polaris-java available.
 *
 * Copyright (C) 2021 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.lane;

import com.google.protobuf.InvalidProtocolBufferException;
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.common.InitContext;
import com.tencent.polaris.api.plugin.route.RouteInfo;
import com.tencent.polaris.api.plugin.route.RouteResult;
import com.tencent.polaris.api.pojo.*;
import com.tencent.polaris.api.rpc.RequestBaseEntity;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.CompareUtils;
import com.tencent.polaris.api.utils.RuleUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.flow.BaseFlow;
import com.tencent.polaris.client.flow.DefaultFlowControlParam;
import com.tencent.polaris.client.flow.ResourcesResponse;
import com.tencent.polaris.logging.LoggerFactory;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataContainer;
import com.tencent.polaris.metadata.core.MetadataType;
import com.tencent.polaris.metadata.core.TransitiveType;
import com.tencent.polaris.metadata.core.manager.MetadataContext;
import com.tencent.polaris.metadata.core.manager.MetadataContextHolder;
import com.tencent.polaris.plugins.router.common.AbstractServiceRouter;
import com.tencent.polaris.specification.api.v1.service.manage.ResponseProto;
import com.tencent.polaris.specification.api.v1.traffic.manage.LaneProto;
import com.tencent.polaris.specification.api.v1.traffic.manage.RoutingProto;
import org.slf4j.Logger;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class LaneRouter extends AbstractServiceRouter {

    private static final Logger LOG = LoggerFactory.getLogger(LaneRouter.class);

    /**
     * 处于泳道内的实例标签
     */
    public static final String INTERNAL_INSTANCE_LANE_KEY = "lane";

    /**
     * 泳道染色标签
     */
    public static final String TRAFFIC_STAIN_LABEL = "service-lane";

    private static final String GATEWAY_SELECTOR = "polarismesh.cn/gateway/spring-cloud-gateway";

    private static final String SERVICE_SELECTOR = "polarismesh.cn/service";

    private final Function> ruleGetter = serviceKey -> {
        if (Objects.isNull(serviceKey)) {
            return Collections.emptyList();
        }
        if (StringUtils.isBlank(serviceKey.getService()) || StringUtils.isBlank(serviceKey.getNamespace())) {
            return Collections.emptyList();
        }

        DefaultFlowControlParam engineFlowControlParam = new DefaultFlowControlParam();
        BaseFlow.buildFlowControlParam(new RequestBaseEntity(), extensions.getConfiguration(), engineFlowControlParam);
        Set routerKeys = new HashSet<>();
        ServiceEventKey dstSvcEventKey = ServiceEventKey.builder().serviceKey(serviceKey).eventType(ServiceEventKey.EventType.LANE_RULE).build();
        routerKeys.add(dstSvcEventKey);
        DefaultServiceEventKeysProvider svcKeysProvider = new DefaultServiceEventKeysProvider();
        svcKeysProvider.setSvcEventKeys(routerKeys);
        ResourcesResponse resourcesResponse = BaseFlow
                .syncGetResources(extensions, false, svcKeysProvider, engineFlowControlParam);
        ServiceRule outbound = resourcesResponse.getServiceRule(dstSvcEventKey);
        Object rule = outbound.getRule();
        if (Objects.nonNull(rule)) {
            return ((ResponseProto.DiscoverResponse) rule).getLanesList();
        }
        return Collections.emptyList();
    };

    @Override
    public void init(InitContext ctx) throws PolarisException {

    }

    @Override
    public String getName() {
        return ServiceRouterConfig.DEFAULT_ROUTER_LANE;
    }


    @Override
    public Aspect getAspect() {
        return Aspect.MIDDLE;
    }

    @Override
    public RouteResult router(RouteInfo routeInfo, ServiceInstances instances) throws PolarisException {
        MetadataContext manager = MetadataContextHolder.getOrCreate();
        MessageMetadataContainer callerMsgContainer = manager.getMetadataContainer(MetadataType.MESSAGE, true);
        MessageMetadataContainer calleeMsgContainer = manager.getMetadataContainer(MetadataType.MESSAGE, false);
        ServiceKey caller = routeInfo.getSourceService() == null ? null : routeInfo.getSourceService().getServiceKey();
        ServiceKey callee = instances.getServiceKey() == null ? null : instances.getServiceKey();

        LaneRuleContainer container = fetchLaneRules(manager, caller, callee);

        // get callee lane group list
        List laneGroupList = container.getGroupListByCalleeNamespaceAndService(callee);

        // 判断当前流量是否已存在染色
        String stainLabel = callerMsgContainer.getHeader(TRAFFIC_STAIN_LABEL);
        boolean alreadyStain = StringUtils.isNotBlank(stainLabel);

        Optional targetRule;
        if (alreadyStain) {
            targetRule = container.matchRule(stainLabel);
        } else {
            // 当前流量无染色,根据泳道规则进行匹配判断
            targetRule = container.matchRule(routeInfo, manager);
        }

        // 泳道规则不存在,转为基线路由
        if (!targetRule.isPresent()) {
            return new RouteResult(redirectToBase(laneGroupList, instances), RouteResult.State.Next);
        }

        LaneProto.LaneRule laneRule = targetRule.get();
        // 尝试进行流量染色动作,该操作仅在当前 Caller 服务为泳道入口时操作
        boolean stainOK = tryStainCurrentTraffic(manager, caller, container, laneRule);
        if (!stainOK) {
            // 如果染色失败,即当前 Caller 不是泳道入口,不需要进行染色,只需要将已有的泳道标签进行透传
            if (alreadyStain) {
                calleeMsgContainer.setHeader(TRAFFIC_STAIN_LABEL, stainLabel, TransitiveType.PASS_THROUGH);
            } else {
                LOG.debug("current traffic not in lane, redirect to base, caller: {} callee: {}", caller, instances.getServiceKey());
                // 如果当前自己不是泳道入口,并且没有发现已经染色的标签,不能走泳道路由,
                return new RouteResult(redirectToBase(laneGroupList, instances), RouteResult.State.Next);
            }
        }

        List resultInstances = tryRedirectToLane(container, laneRule, laneGroupList, instances);
        if (CollectionUtils.isNotEmpty(resultInstances)) {
            return new RouteResult(resultInstances, RouteResult.State.Next);
        }
        // 严格模式,返回空实例列表
        if (laneRule.getMatchMode() == LaneProto.LaneRule.LaneMatchMode.STRICT) {
            return new RouteResult(Collections.emptyList(), RouteResult.State.Next);
        }
        // 宽松模式,降级为返回基线实例
        return new RouteResult(redirectToBase(laneGroupList, instances), RouteResult.State.Next);
    }

    private List tryRedirectToLane(LaneRuleContainer container, LaneProto.LaneRule rule,
                                             List laneGroupList, ServiceInstances instances) {
        LaneProto.LaneGroup group = container.groups.get(rule.getGroupName());
        if (Objects.isNull(group)) {
            LOG.debug("not found lane_group, redirect to base, lane_rule: {}, lane_group: {}, callee: {}", rule.getName(), rule.getGroupName(), instances.getServiceKey());
            // 泳道组不存在,直接认为不需要过滤实例, 默认转发至基线实例
            return redirectToBase(laneGroupList, instances);
        }
        // 判断目标服务是否属于泳道内服务
        boolean inLane = false;
        ServiceKey callee = instances.getServiceKey();
        for (RoutingProto.DestinationGroup destination : group.getDestinationsList()) {
            if (RuleUtils.matchService(callee, destination.getNamespace(), destination.getService())) {
                inLane = true;
                break;
            }
        }

        // 不在泳道内的服务,不需要进行实例过滤, 默认转发至基线实例
        if (!inLane) {
            LOG.debug("current traffic not in lane, redirect to base, lane_rule: {}, lane_group: {}, callee: {}", rule.getName(), rule.getGroupName(), instances.getServiceKey());
            return redirectToBase(laneGroupList, instances);
        }

        return instances.getInstances().stream().filter(instance -> {
            Map metadata = instance.getMetadata();
            if (CollectionUtils.isEmpty(metadata)) {
                return false;
            }
            String labelKey = StringUtils.isNotBlank(rule.getLabelKey()) ? rule.getLabelKey() : INTERNAL_INSTANCE_LANE_KEY;
            String val = metadata.get(labelKey);
            String defaultLabelValue = rule.getDefaultLabelValue();
            Set defaultLabelValues = new HashSet<>(Arrays.asList(defaultLabelValue.split(",")));
            return defaultLabelValues.contains(val);
        }).collect(Collectors.toList());
    }

    private List redirectToBase(List laneGroupList, ServiceInstances instances) {
        return instances.getInstances().stream().filter(instance -> {
            Map metadata = instance.getMetadata();
            if (CollectionUtils.isEmpty(metadata)) {
                return true;
            }

            boolean inBase = true;
            for (LaneProto.LaneGroup laneGroup : laneGroupList) {
                Map> laneKeyValueMap = new HashMap<>();
                for (LaneProto.LaneRule laneRule : laneGroup.getRulesList()) {
                    String labelKey = StringUtils.isNotBlank(laneRule.getLabelKey()) ? laneRule.getLabelKey() : INTERNAL_INSTANCE_LANE_KEY;
                    if (!laneKeyValueMap.containsKey(labelKey)) {
                        laneKeyValueMap.put(labelKey, new HashSet<>());
                    }
                    String defaultLabelValue = laneRule.getDefaultLabelValue();
                    String[] split = defaultLabelValue.split(",");
                    laneKeyValueMap.get(labelKey).addAll(Arrays.asList(split));
                }
                if (CollectionUtils.isNotEmpty(laneKeyValueMap)) {
                    for (Map.Entry entry : metadata.entrySet()) {
                        if (laneKeyValueMap.containsKey(entry.getKey()) && laneKeyValueMap.get(entry.getKey()).contains(entry.getValue())) {
                            inBase = false;
                            break;
                        }
                    }
                }
                if (!inBase) {
                    return false;
                }
            }
            return true;
        }).collect(Collectors.toList());
    }

    private boolean tryStainCurrentTraffic(MetadataContext manager, ServiceKey caller, LaneRuleContainer container, LaneProto.LaneRule rule) {
        if (Objects.isNull(caller)) {
            LOG.debug("caller is null, stain current traffic ignore, lane_rule: {}, lane_group: {}", rule.getName(), rule.getGroupName());
            return false;
        }

        LaneProto.LaneGroup group = container.groups.get(rule.getGroupName());
        if (Objects.isNull(group)) {
            // 泳道规则存在,但是对应的泳道组却不存在,这种情况需要直接抛出异常
            LOG.error("lane_group where lane_rule located not found, lane_rule: {}, lane_group: {}", rule.getName(), rule.getGroupName());
            throw new PolarisException(ErrorCode.INVALID_STATE, "lane_group where lane_rule located not found");
        }

        boolean needStain = isTrafficEntry(group, manager, caller);
        if (needStain) {
            MessageMetadataContainer metadataContainer = manager.getMetadataContainer(MetadataType.MESSAGE, false);
            metadataContainer.setHeader(TRAFFIC_STAIN_LABEL, buildStainLabel(rule), TransitiveType.PASS_THROUGH);
        }
        LOG.debug("stain current traffic: {}, lane_rule: {}, lane_group: {}, caller: {}", needStain, rule.getName(), rule.getGroupName(), caller);
        return needStain;
    }

    private LaneRuleContainer fetchLaneRules(MetadataContext manager, ServiceKey caller, ServiceKey callee) {
        // 获取泳道规则
        List result = new ArrayList<>();
        if (Objects.nonNull(caller)) {
            result.addAll(ruleGetter.apply(caller));
        }
        if (Objects.nonNull(callee)) {
            result.addAll(ruleGetter.apply(callee));
        }
        return new LaneRuleContainer(manager, caller, result);
    }

    private static boolean isTrafficEntry(LaneProto.LaneGroup group, MetadataContext manager, ServiceKey caller) {
        boolean result = false;
        for (LaneProto.TrafficEntry entry : group.getEntriesList()) {
            try {
                switch (entry.getType()) {
                    case GATEWAY_SELECTOR:
                        LaneProto.ServiceGatewaySelector gatewaySelector = entry.getSelector().unpack(LaneProto.ServiceGatewaySelector.class);
                        if (RuleUtils.matchService(caller, gatewaySelector.getNamespace(), gatewaySelector.getService())
                                && RuleUtils.matchMetadata(gatewaySelector.getLabelsMap(), null, manager.getMetadataContainerGroup(false))) {
                            result = true;
                        }
                        break;
                    case SERVICE_SELECTOR:
                        LaneProto.ServiceSelector serviceSelector = entry.getSelector().unpack(LaneProto.ServiceSelector.class);
                        if (RuleUtils.matchService(caller, serviceSelector.getNamespace(), serviceSelector.getService())
                                && RuleUtils.matchMetadata(serviceSelector.getLabelsMap(), null, manager.getMetadataContainerGroup(false))) {
                            result = true;
                        }
                        break;
                }
            } catch (InvalidProtocolBufferException invalidProtocolBufferException) {
                LOG.warn("lane_group: {} unpack traffic entry selector fail", group.getName(), invalidProtocolBufferException);
            }
        }
        return result;
    }

    private static class LaneRuleContainer {
        private final Map groups = new HashMap<>();

        private final List rules = new LinkedList<>();

        private final Map ruleMapping = new HashMap<>();

        LaneRuleContainer(MetadataContext manager, ServiceKey caller, List list) {
            list.forEach(laneGroup -> {
                if (groups.containsKey(laneGroup.getName())) {
                    LOG.warn("lane group: {} duplicate, ignore", laneGroup.getName());
                    return;
                }
                groups.put(laneGroup.getName(), laneGroup);
                laneGroup.getRulesList().forEach(laneRule -> {
                    if (!laneRule.getEnable()) {
                        return;
                    }
                    String name = buildStainLabel(laneRule);
                    ruleMapping.put(name, laneRule);
                    rules.add(laneRule);
                });
            });

            rules.sort((o1, o2) -> {
                // 主调泳道入口规则优先
                boolean b1 = isTrafficEntry(groups.get(o1.getGroupName()), manager, caller);
                boolean b2 = isTrafficEntry(groups.get(o2.getGroupName()), manager, caller);
                int entryResult = CompareUtils.compareBoolean(b1, b2);
                if (entryResult != 0) {
                    return entryResult;
                }

                // 比较优先级,数字越小,规则优先级越大
                return o1.getPriority() - o2.getPriority();
            });
        }

        public List getGroupListByCalleeNamespaceAndService(ServiceKey callee) {
            List groupList = new ArrayList<>();
            for (LaneProto.LaneGroup group : groups.values()) {
                for (RoutingProto.DestinationGroup destinationGroup : group.getDestinationsList()) {
                    if (RuleUtils.matchService(callee, destinationGroup.getNamespace(), destinationGroup.getService())) {
                        groupList.add(group);
                    }
                }
            }
            return groupList;
        }

        public Optional matchRule(String labelValue) {
            LaneProto.LaneRule rule = ruleMapping.get(labelValue);
            return Optional.ofNullable(rule);
        }

        public Optional matchRule(RouteInfo routeInfo, MetadataContext manager) {
            // 当前流量无染色,根据泳道规则进行匹配判断
            LaneProto.LaneRule targetRule = null;
            for (LaneProto.LaneRule rule : rules) {
                if (!rule.getEnable()) {
                    continue;
                }

                LaneProto.TrafficMatchRule matchRule = rule.getTrafficMatchRule();

                List booleans = new LinkedList<>();
                matchRule.getArgumentsList().forEach(sourceMatch -> {
                    String trafficValue = findTrafficValue(routeInfo, sourceMatch, manager);
                    switch (sourceMatch.getValue().getValueType()) {
                        case TEXT:
                            // 直接匹配
                            boolean a = StringUtils.isNotBlank(trafficValue) &&
                                    RuleUtils.matchStringValue(sourceMatch.getValue().getType(), trafficValue,
                                            sourceMatch.getValue().getValue().getValue());
                            booleans.add(a);
                            break;
                        case VARIABLE:
                            boolean match = false;
                            String parameterKey = sourceMatch.getValue().getValue().getValue();
                            // 外部参数来源
                            Optional parameter = routeInfo.getExternalParameterSupplier().apply(parameterKey);
                            if (parameter.isPresent()) {
                                match = RuleUtils.matchStringValue(sourceMatch.getValue().getType(), trafficValue,
                                        parameter.get());
                            }
                            if (!match) {
                                match = RuleUtils.matchStringValue(sourceMatch.getValue().getType(), trafficValue,
                                        System.getenv(parameterKey));
                            }
                            booleans.add(match);
                            break;
                    }
                });

                boolean isMatched = false;
                switch (matchRule.getMatchMode()) {
                    case OR:
                        for (Boolean a : booleans) {
                            isMatched = isMatched || a;
                        }
                        break;
                    case AND:
                        isMatched = true;
                        for (Boolean a : booleans) {
                            isMatched = isMatched && a;
                        }
                        break;
                }

                if (!isMatched) {
                    continue;
                }
                targetRule = rule;
                break;
            }

            return Optional.ofNullable(targetRule);
        }
    }

    private static String findTrafficValue(RouteInfo routeInfo, RoutingProto.SourceMatch sourceMatch, MetadataContext manager) {
        Map trafficLabels = routeInfo.getRouterMetadata(ServiceRouterConfig.DEFAULT_ROUTER_LANE);

        MessageMetadataContainer calleeMessageContainer = manager.getMetadataContainer(MetadataType.MESSAGE, false);
        MetadataContainer calleeCustomContainer = manager.getMetadataContainer(MetadataType.CUSTOM, false);
        MessageMetadataContainer callerMessageContainer = manager.getMetadataContainer(MetadataType.MESSAGE, true);

        String trafficValue = "";
        switch (sourceMatch.getType()) {
            case HEADER:
                String headerKey = RouteArgument.ArgumentType.HEADER.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(headerKey)) {
                    return trafficLabels.get(headerKey);
                }
                trafficValue = Optional.ofNullable(calleeMessageContainer.getHeader(sourceMatch.getKey())).orElse(callerMessageContainer.getHeader(sourceMatch.getKey()));
                break;
            case CUSTOM:
                String customKey = RouteArgument.ArgumentType.CUSTOM.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(customKey)) {
                    return trafficLabels.get(customKey);
                }
                trafficValue = Optional.ofNullable(calleeCustomContainer.getRawMetadataStringValue(sourceMatch.getKey())).orElse("");
                break;
            case METHOD:
                String methodKey = RouteArgument.ArgumentType.METHOD.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(methodKey)) {
                    return trafficLabels.get(methodKey);
                }
                trafficValue = Optional.ofNullable(calleeMessageContainer.getMethod()).orElse(callerMessageContainer.getMethod());
                break;
            case CALLER_IP:
                String callerIpKey = RouteArgument.ArgumentType.CALLER_IP.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(callerIpKey)) {
                    return trafficLabels.get(callerIpKey);
                }
                trafficValue = Optional.ofNullable(calleeMessageContainer.getCallerIP()).orElse(callerMessageContainer.getCallerIP());
                break;
            case COOKIE:
                String cookieKey = RouteArgument.ArgumentType.COOKIE.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(cookieKey)) {
                    return trafficLabels.get(cookieKey);
                }
                trafficValue = Optional.ofNullable(calleeMessageContainer.getCookie(sourceMatch.getKey())).orElse(callerMessageContainer.getCookie(sourceMatch.getKey()));
                break;
            case QUERY:
                String queryKey = RouteArgument.ArgumentType.QUERY.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(queryKey)) {
                    return trafficLabels.get(queryKey);
                }
                trafficValue = Optional.ofNullable(calleeMessageContainer.getQuery(sourceMatch.getKey())).orElse(callerMessageContainer.getQuery(sourceMatch.getKey()));
                break;
            case PATH:
                String pathKey = RouteArgument.ArgumentType.PATH.key(sourceMatch.getKey());
                if (trafficLabels.containsKey(pathKey)) {
                    return trafficLabels.get(pathKey);
                }
                trafficValue = Optional.ofNullable(calleeMessageContainer.getPath()).orElse(callerMessageContainer.getPath());
                break;
        }
        return trafficValue;
    }

    private static String buildStainLabel(LaneProto.LaneRule rule) {
        return rule.getGroupName() + "/" + rule.getName();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy