前言
之前分析了Dubbo路由层的Directory,它的工作主要是负责获取服务提供者列表。回忆一下dubbo官网介绍的图:
下一步的操作是在Router接口这里,今天来看一下它主要做哪些工作。
Router
先看官方文档怎么说:
Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等
之前Directory获取了当前可用的服务提供者,Router则再从里按规则过滤获取需要的提供者。
读写分离,应用隔离,很容易就想到管理dubbo服务相关操作,即我们在管理后台里的禁用等功能可能就是这里做了文章。
看一下接口设计:
public interface Router extends Comparable<Router> {
/**
* get the router url.
*
* @return url
*/
URL getUrl();
/**
* route.
*
* @param invokers
* @param url refer url
* @param invocation
* @return routed invokers
* @throws RpcException
*/
<T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
就俩方法:
- 获取url
- 路由
再看其实现的子类有哪些: 
以及Router家族谱: 
三个实现:
- ScriptRouter
- ConditionRouter
- MockInvokersSelector
MockInvokersSelector
先看这个类,看名称就知道跟降级有关。
实现的两个方法之getUrl:
public URL getUrl() {
return null;
}
可以说根本用不到这个方法。
实现的两个方法之route:
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
URL url, final Invocation invocation) throws RpcException {
if (invocation.getAttachments() == null) {
return getNormalInvokers(invokers); // 获取正常的提供者
} else {
String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
if (value == null)
return getNormalInvokers(invokers); // 获取正常的提供者
else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
return getMockedInvokers(invokers); // 获取降级的提供者,即协议是mock
}
}
return invokers;
}
这也正如注释所说:
A specific Router designed to realize mock feature.
If a request is configured to use mock, then this router guarantees that only the invokers with protocol MOCK appear in final the invoker list, all other invokers will be excluded.
如果url请求被配置为mock,那么只有协议是mock会被保留到服务提供者列表,其他都被排除。代码如下:
private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
if (!hasMockProviders(invokers)) {
return null;
}
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
for (Invoker<T> invoker : invokers) {
if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
sInvokers.add(invoker);
}
}
return sInvokers;
}
ConditionRouter
条件路由。
基于条件表达式的路由规则,如:host = 10.20.153.10 => host = 10.20.153.11
关于规则以及表达式写法介绍,请参考dubbo用户文档手册。
管理控制台使用示例
准备工作:启动两个provider(本地单机模拟,仅修改端口号,生产需要两个节点上部署),控制台显示如下: 
再启动consumer可见控制台: 
管理控制台添加路由规则:
这个规则的意义是把 192.168.99.243添加到黑名单host中。
保存后此时看consumer日志:
可以看到注册中心发起通知给所有消费者,将该Host的服务添加到黑名单中,由于本地开发机只有该Host的两个服务,于是都被加入黑名单后没有可用的提供者,于是报错。
源码分析
根据日志和debug工具,我们很容易定位到何时触发通知,即之前分析过的RegistryDirectory类的notify方法。

如上图所示,这里看到标注的toRouters 方法,将url转成Router对象。
private List<Router> toRouters(List<URL> urls) {
List<Router> routers = new ArrayList<Router>();
if (urls == null || urls.isEmpty()) {
return routers;
}
if (urls != null && !urls.isEmpty()) {
for (URL url : urls) {
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
String routerType = url.getParameter(Constants.ROUTER_KEY);
if (routerType != null && routerType.length() > 0) {
url = url.setProtocol(routerType);
}
try {
Router router = routerFactory.getRouter(url); // 将url转为Router对象
if (!routers.contains(router))
routers.add(router);
} catch (Throwable t) {
logger.error("convert router url to router error, url: " + url, t);
}
}
}
return routers;
}
这里的routerFactory工厂为ConditionRouterFactory,所以这里获取的Router对象为ConditionRouterFactory.其创造的对象是构造器创建:
public ConditionRouter(URL url) {
this.url = url;
this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
this.force = url.getParameter(Constants.FORCE_KEY, false);
try {
String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
if (rule == null || rule.trim().length() == 0) {
throw new IllegalArgumentException("Illegal route rule!");
}
rule = rule.replace("consumer.", "").replace("provider.", "");
int i = rule.indexOf("=>");
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
// NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
this.whenCondition = when;
this.thenCondition = then;
} catch (ParseException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
可见这里针对url做了解析,可见url在dubbo中是多么核心的存在。具体的解析在方法parseRule(String rule)中,就不贴代码了。其次,根据debug工具可见其调用栈,直接截图:




解释下,画个时序图方便整理: 
步骤解析:
- 当我们在控制台动态增加一条路由规则时,将会触发
RegistryDirectory的notify()方法。 - 在
notify()内调用setRouter()方法,将通过ConditionRouterFactory获取ConditionRouter路由对象并添加到路由列表。 - 继续调用
refreshInvoker()方法,这个方法之前介绍过,关键步骤在于toMethodInvokers()获取一个方法名与提供者列表的映射列表。 - 调用内部
route()方法开始获取可用提供者列表。 RegistryDirectory的notify()方法内部实际上是调用了ConditionRouter对象的route方法过滤获取最终可用提供者列表。
ScriptRouter
脚本路由规则,这个使用的不是很多,这里引用官网截图: 
可以简单看一下源码里:
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
try {
List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers);
Compilable compilable = (Compilable) engine;
Bindings bindings = engine.createBindings();
bindings.put("invokers", invokersCopy);
bindings.put("invocation", invocation);
bindings.put("context", RpcContext.getContext());
CompiledScript function = compilable.compile(rule);
Object obj = function.eval(bindings);
if (obj instanceof Invoker[]) {
invokersCopy = Arrays.asList((Invoker<T>[]) obj);
} else if (obj instanceof Object[]) {
invokersCopy = new ArrayList<Invoker<T>>();
for (Object inv : (Object[]) obj) {
invokersCopy.add((Invoker<T>) inv);
}
} else {
invokersCopy = (List<Invoker<T>>) obj;
}
return invokersCopy;
} catch (ScriptException e) {
//fail then ignore rule .invokers.
logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
return invokers;
}
}
其通过一个ScriptEngine对象,编译了规则获取一个function脚本,其可支持执行eval方法过滤获得提供者列表。
结束语
Router的过程不算复杂,可以发现比较核心的地方还是在之前分析Directory时的notify方法,一切路由规则都是从这触发的。
ps:这次整理的有些慢了,还是不太熟悉画图的过程,画图比较慢,mac上推荐使用OmniGraffle画图,提供的模板还是比较强大的。


