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;
}