Dubbo的路由策略剖析

1 概述

Dubbo中的路由策略的作用是服务消费端使用路由策略对服务提供者列表进行过滤和选择,最终获取符合路由规则的服务提供者。

Dubbo中的路由策略主要分为两类,StateRouter和普通Router。

StateRouter (如TagStateRouter、ConditionStateRouter等)是在Dubbo 3中引入的一种路由实现,是Dubbo 3中主要使用的路由策略。它可根据服务提供者的状态信息(如负载、响应时间等)进行路由决策。

普通Router是指根据预设的路由规则进行服务调用的路由选择。

2 路由策略的加载

服务消费端启动时,会加载远程服务对应的路由规则,并创建路由规则链。

具体而言,在服务消费端启动时,会将远程服务转换为Invoker,在此过程中,会调用 RegistryProtocol 的 doCreateInvoker 方法,其中会调用 RegistryDirectory 的 buildRouterChain() 方法创建路由规则链 RouterChain。具体实现细节如下所示。

java 复制代码
// org.apache.dubbo.registry.integration.RegistryProtocol#getInvoker
public <T> ClusterInvoker<T> getInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // FIXME, this method is currently not used, create the right registry before enable.
    DynamicDirectory<T> directory = new RegistryDirectory<>(type, url);
    return doCreateInvoker(directory, cluster, registry, type);
}

// org.apache.dubbo.registry.integration.RegistryProtocol#doCreateInvoker
protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    // all attributes of REFER_KEY
    Map<String, String> parameters = new HashMap<>(directory.getConsumerUrl().getParameters());
    URL urlToRegistry = new ServiceConfigURL(
        parameters.get(PROTOCOL_KEY) == null ? CONSUMER : parameters.get(PROTOCOL_KEY),
        parameters.remove(REGISTER_IP_KEY),
        0,
        getPath(parameters, type),
        parameters
    );
    urlToRegistry = urlToRegistry.setScopeModel(directory.getConsumerUrl().getScopeModel());
    urlToRegistry = urlToRegistry.setServiceModel(directory.getConsumerUrl().getServiceModel());
    if (directory.isShouldRegister()) {
        directory.setRegisteredConsumerUrl(urlToRegistry);
        registry.register(directory.getRegisteredConsumerUrl());
    }


    // 1、建立路由规则链
    directory.buildRouterChain(urlToRegistry);
    // 2、订阅服务提供者地址,生成invoker
    directory.subscribe(toSubscribeUrl(urlToRegistry));
    // 3、封装集群容错策略到invoker
    return (ClusterInvoker<T>) cluster.join(directory, true);
}

以下为加载路由规则和创建路由规则链的实现细节。

java 复制代码
// org.apache.dubbo.registry.integration.DynamicDirectory#buildRouterChain
public void buildRouterChain(URL url) {
    this.setRouterChain(RouterChain.buildChain(getInterface(), url));
}


// org.apache.dubbo.rpc.cluster.RouterChain#buildChain
public static <T> RouterChain<T> buildChain(Class<T> interfaceClass, URL url) {
    SingleRouterChain<T> chain1 = buildSingleChain(interfaceClass, url);
    SingleRouterChain<T> chain2 = buildSingleChain(interfaceClass, url);
    return new RouterChain<>(new SingleRouterChain[]{chain1, chain2});
}


public static <T> SingleRouterChain<T> buildSingleChain(Class<T> interfaceClass, URL url) {
    ModuleModel moduleModel = url.getOrDefaultModuleModel();

    List<RouterFactory> extensionFactories = moduleModel.getExtensionLoader(RouterFactory.class)
        .getActivateExtension(url, ROUTER_KEY);

    // 加载 Router 路由规则
    List<Router> routers = extensionFactories.stream()
        .map(factory -> factory.getRouter(url))
        .sorted(Router::compareTo)
        .collect(Collectors.toList());

    // 加载 StateRouter 路由规则
    List<StateRouter<T>> stateRouters = moduleModel
        .getExtensionLoader(StateRouterFactory.class)
        .getActivateExtension(url, ROUTER_KEY)
        .stream()
        .map(factory -> factory.getRouter(interfaceClass, url))
        .collect(Collectors.toList());


    boolean shouldFailFast = Boolean.parseBoolean(ConfigurationUtils.getProperty(moduleModel, Constants.SHOULD_FAIL_FAST_KEY, "true"));

    RouterSnapshotSwitcher routerSnapshotSwitcher = ScopeModelUtil.getFrameworkModel(moduleModel).getBeanFactory().getBean(RouterSnapshotSwitcher.class);

    // 创建路由链
    return new SingleRouterChain<>(routers, stateRouters, shouldFailFast, routerSnapshotSwitcher);
}


public SingleRouterChain(List<Router> routers, List<StateRouter<T>> stateRouters, boolean shouldFailFast, RouterSnapshotSwitcher routerSnapshotSwitcher) {
    initWithRouters(routers);

    initWithStateRouters(stateRouters);

    this.shouldFailFast = shouldFailFast;
    this.routerSnapshotSwitcher = routerSnapshotSwitcher;
}

以下为构建 StateRouter 路由链的实现细节。

java 复制代码
private void initWithStateRouters(List<StateRouter<T>> stateRouters) {
    StateRouter<T> stateRouter = TailStateRouter.getInstance();
    for (int i = stateRouters.size() - 1; i >= 0; i--) {
        StateRouter<T> nextStateRouter = stateRouters.get(i);
        // 设置当前路由节点的下一个路由节点
        nextStateRouter.setNextRouter(stateRouter);
        stateRouter = nextStateRouter;
    }
    this.headStateRouter = stateRouter;
    this.stateRouters = Collections.unmodifiableList(stateRouters);
}

3 路由策略的使用

服务消费端根据负载均衡策略获取服务提供者前,会先根据路由策略获取符合路由规则的服务提供者。具体细节如下所示。

(1)以下是服务消费端发起远程调用的主要过程

org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke

java 复制代码
public Result invoke(final Invocation invocation) throws RpcException {
    checkWhetherDestroyed();

    // binding attachments into invocation.
//        Map<String, Object> contextAttachments = RpcContext.getClientAttachment().getObjectAttachments();
//        if (contextAttachments != null && contextAttachments.size() != 0) {
//            ((RpcInvocation) invocation).addObjectAttachmentsIfAbsent(contextAttachments);
//        }

    InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Router route.");

    // 1、获取服务提供者列表-Invokers
    List<Invoker<T>> invokers = list(invocation);
    InvocationProfilerUtils.releaseDetailProfiler(invocation);

    checkInvokers(invokers, invocation);

    // 2、获取负载均衡策略。根据url参数找LoadBalance扩展,默认RandomLoadBalance
    LoadBalance loadbalance = initLoadBalance(invokers, invocation);
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);

    InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Cluster " + this.getClass().getName() + " invoke.");
    try {
        // 3、执行远程调用。子类实现,会有不同的集群容错方式
        return doInvoke(invocation, invokers, loadbalance);
    } finally {
        InvocationProfilerUtils.releaseDetailProfiler(invocation);
    }
}


protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
    return getDirectory().list(invocation);
}

(2)以下是根据路由策略获取符合路由规则的服务提供者

org.apache.dubbo.rpc.cluster.directory.AbstractDirectory#list

java 复制代码
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
    if (destroyed) {
        throw new RpcException("Directory of type " + this.getClass().getSimpleName() + " already destroyed for service " + getConsumerUrl().getServiceKey() + " from registry " + getUrl());
    }

    BitList<Invoker<T>> availableInvokers;
    SingleRouterChain<T> singleChain = null;
    try {
        try {
            if (routerChain != null) {
                routerChain.getLock().readLock().lock();
            }
            // use clone to avoid being modified at doList().
            if (invokersInitialized) {
                availableInvokers = validInvokers.clone();
            } else {
                availableInvokers = invokers.clone();
            }

            // 获取路由规则链
            if (routerChain != null) {
                singleChain = routerChain.getSingleChain(getConsumerUrl(), availableInvokers, invocation);
                singleChain.getLock().readLock().lock();
            }
        } finally {
            if (routerChain != null) {
                routerChain.getLock().readLock().unlock();
            }
        }

        // 根据路由规则信息和invoker列表,获取经过路由规则筛选后的服务提供者列表
        List<Invoker<T>> routedResult = doList(singleChain, availableInvokers, invocation);
        if (routedResult.isEmpty()) {
            // 2-2 - No provider available.

            logger.warn(CLUSTER_NO_VALID_PROVIDER, "provider server or registry center crashed", "",
                "No provider available after connectivity filter for the service " + getConsumerUrl().getServiceKey()
                    + " All routed invokers' size: " + routedResult.size()
                    + " from registry " + this
                    + " on the consumer " + NetUtils.getLocalHost()
                    + " using the dubbo version " + Version.getVersion() + ".");
        }
        return Collections.unmodifiableList(routedResult);
    } finally {
        if (singleChain != null) {
            singleChain.getLock().readLock().unlock();
        }
    }
}

org.apache.dubbo.registry.integration.DynamicDirectory#doList

java 复制代码
public List<Invoker<T>> doList(SingleRouterChain<T> singleRouterChain,
                               BitList<Invoker<T>> invokers, Invocation invocation) {
    if (forbidden && shouldFailFast) {
        // 1. No service provider 2. Service providers are disabled
        throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
            this + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
            NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
            ", please check status of providers(disabled, not registered or in blacklist).");
    }

    if (multiGroup) {
        return this.getInvokers();
    }

    try {
        // 执行路由策略,获取服务提供者列表
        // Get invokers from cache, only runtime routers will be executed.
        List<Invoker<T>> result = singleRouterChain.route(getConsumerUrl(), invokers, invocation);
        return result == null ? BitList.emptyList() : result;
    } catch (Throwable t) {
        // 2-1 - Failed to execute routing.
        logger.error(CLUSTER_FAILED_SITE_SELECTION, "", "",
            "Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);

        return BitList.emptyList();
    }
}

org.apache.dubbo.rpc.cluster.SingleRouterChain#route

java 复制代码
public List<Invoker<T>> route(URL url, BitList<Invoker<T>> availableInvokers, Invocation invocation) {
    if (invokers.getOriginList() != availableInvokers.getOriginList()) {
        logger.error(INTERNAL_ERROR, "", "Router's invoker size: " + invokers.getOriginList().size() +
                " Invocation's invoker size: " + availableInvokers.getOriginList().size(),
            "Reject to route, because the invokers has changed.");
        throw new IllegalStateException("reject to route, because the invokers has changed.");
    }
    if (RpcContext.getServiceContext().isNeedPrintRouterSnapshot()) {
        return routeAndPrint(url, availableInvokers, invocation);
    } else {
        return simpleRoute(url, availableInvokers, invocation);
    }
}


public List<Invoker<T>> simpleRoute(URL url, BitList<Invoker<T>> availableInvokers, Invocation invocation) {
    BitList<Invoker<T>> resultInvokers = availableInvokers.clone();

    // 1. route state router
    resultInvokers = headStateRouter.route(resultInvokers, url, invocation, false, null);
    if (resultInvokers.isEmpty() && (shouldFailFast || routers.isEmpty())) {
        printRouterSnapshot(url, availableInvokers, invocation);
        return BitList.emptyList();
    }

    if (routers.isEmpty()) {
        return resultInvokers;
    }
    List<Invoker<T>> commonRouterResult = resultInvokers.cloneToArrayList();
    // 2. route common router
    for (Router router : routers) {
        // Copy resultInvokers to a arrayList. BitList not support
        RouterResult<Invoker<T>> routeResult = router.route(commonRouterResult, url, invocation, false);
        commonRouterResult = routeResult.getResult();
        if (CollectionUtils.isEmpty(commonRouterResult) && shouldFailFast) {
            printRouterSnapshot(url, availableInvokers, invocation);
            return BitList.emptyList();
        }

        // stop continue routing
        if (!routeResult.isNeedContinueRoute()) {
            return commonRouterResult;
        }
    }

    if (commonRouterResult.isEmpty()) {
        printRouterSnapshot(url, availableInvokers, invocation);
        return BitList.emptyList();
    }

    return commonRouterResult;
}

使用基于BitMap实现的BitList,对不同路由策略之间的结果取交集(&),得到最终的路由结果。具体实现如下所示。

org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter#route

java 复制代码
public final BitList<Invoker<T>> route(BitList<Invoker<T>> invokers, URL url, Invocation invocation, boolean needToPrintMessage, Holder<RouterSnapshotNode<T>> nodeHolder) throws RpcException {
    if (needToPrintMessage && (nodeHolder == null || nodeHolder.get() == null)) {
        needToPrintMessage = false;
    }

    RouterSnapshotNode<T> currentNode = null;
    RouterSnapshotNode<T> parentNode = null;
    Holder<String> messageHolder = null;

    // pre-build current node
    if (needToPrintMessage) {
        parentNode = nodeHolder.get();
        currentNode = new RouterSnapshotNode<>(this.getClass().getSimpleName(), invokers.clone());
        parentNode.appendNode(currentNode);

        // set parent node's output size in the first child invoke
        // initial node output size is zero, first child will override it
        if (parentNode.getNodeOutputSize() < invokers.size()) {
            parentNode.setNodeOutputInvokers(invokers.clone());
        }

        messageHolder = new Holder<>();
        nodeHolder.set(currentNode);
    }
    BitList<Invoker<T>> routeResult;

    routeResult = doRoute(invokers, url, invocation, needToPrintMessage, nodeHolder, messageHolder);
    if (routeResult != invokers) {
        // 对不同的路由策略之间的结果取交集(&)
        routeResult = invokers.and(routeResult);
    }
    // check if router support call continue route by itself
    if (!supportContinueRoute()) {
        // use current node's result as next node's parameter
        if (!shouldFailFast || !routeResult.isEmpty()) {
            routeResult = continueRoute(routeResult, url, invocation, needToPrintMessage, nodeHolder);
        }
    }

    // post-build current node
    if (needToPrintMessage) {
        currentNode.setRouterMessage(messageHolder.get());
        if (currentNode.getNodeOutputSize() == 0) {
            // no child call
            currentNode.setNodeOutputInvokers(routeResult.clone());
        }
        currentNode.setChainOutputInvokers(routeResult.clone());
        nodeHolder.set(parentNode);
    }
    return routeResult;
}
相关推荐
《黑巧克力》1 天前
【JavaEE】多线程进阶
java·spring·java-ee·maven·dubbo·idea
菜鸟long1 天前
Dubbo源码解析-过滤器Filter
dubbo
基哥的奋斗历程3 天前
springboot整合Camunda实现业务
java·spring boot·dubbo
迷茫的羔羊羊4 天前
Dubbo负载均衡策略都有哪些(简单描述)
运维·负载均衡·dubbo
泰迪智能科技7 天前
泰迪智能科技实验室产品-云计算资源管理平台介绍
科技·云计算·dubbo
怪兽也会哭哭7 天前
微服务应用与开发知识点练习【Gateway,OpenFeign,Dubbo,RocketMQ和RabbitMQ,JPA,Redis,Mycat】
微服务·gateway·rabbitmq·dubbo·rocketmq
哒不溜-w17 天前
springBoot+mongoDB项目中,使用MongoFactory、MongoTemplate分页条件查询,增删查改
spring boot·mongodb·dubbo
中国胖子风清扬19 天前
【实战指南】SpringBoot结合Zookeeper/Nacos构建Dubbo微服务
spring boot·分布式·spring cloud·微服务·zookeeper·dubbo·java-zookeeper
文天大人20 天前
Dubbo-使用zookeeper作为注册中心时节点的概述
dubbo
wudongfang66621 天前
ensp防火墙web密码重置(前提通过console可以登录)
dubbo