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;
}
相关推荐
刘Java3 天前
Dubbo 3.x源码(26)—Dubbo服务引用源码(9)应用级服务发现订阅refreshServiceDiscoveryInvoker
java·dubbo·dubbo源码
码农老起11 天前
从RocketMQ到Dubbo:自研中间件技术的崛起
中间件·dubbo·rocketmq
huahailing102412 天前
apache-dubbo
apache·dubbo
博洋科技12 天前
关于网站的权重和百度蜘蛛爬虫的关系
小程序·dubbo·网站建设·1024程序员节·保定h5网站建设·保定网站建设
B1nna12 天前
外卖开发(七)——校验收货地址是否超出配送范围
开发语言·dubbo·lua
唐梓航-求职中15 天前
rpc-dubbo-多版本
网络协议·rpc·dubbo
西岭千秋雪_18 天前
Dubbo应用篇
java·微服务·dubbo
写bug写bug20 天前
一文搞懂分布式服务发布和引用(Dubbo 案例解读)
java·后端·dubbo
运维&陈同学20 天前
【Dubbo03】消息队列与微服务之dubbo-admin 二进制与编译安装
linux·运维·服务器·后端·微服务·云原生·架构·dubbo
lizz227624 天前
百度地图JSAPI WebGL v1.0类参考
dubbo·webgl