本文基于dubbo v2.6.x
文章目录
- 1. dubbo路由介绍
- 1.1 路由接口
- 1.2 路由实现
- 2. 路由添加与修改
- 2.1添加路由
- 2.2 路由修改
- 2.3 使用路由过滤
- 3.AbstractRouter源码解析
1. dubbo路由介绍
在dubbo中我们可以通过配置路由规则,来控制服务调用者调用哪些服务提供者或者那个服务提供者。引用官方文档上的一句话:“路由规则在发起一次RPC调用前起到过滤目标服务器地址的作用,过滤后的地址列表,将作为消费端最终发起RPC调用的备选地址。(官方文档地址:链接)”
1.1 路由接口
这里我们先看下dubbo提供的路由工厂接口RouterFactory:
@SPI
public interface RouterFactory {
/**
* Create router.
* 创建router对象方法
* @param url
* @return router
*/
@Adaptive("protocol")
Router getRouter(URL url);
}
可以看出RouterFactory 路由工厂接口是一个dubbo spi 扩展点,抽象了获取路由规则的getRouter(url)方法。
接下来再来看看路由规则的接口Router:
public interface Router extends Comparable<Router>{
/**
* get the router url.
* 获得url
* @return url
*/
URL getUrl();
/**
* route.
* 根据规则筛选invoker
* @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;
/**
* Router's priority, used to sort routers.
*
* @return router's priority
*/
int getPriority();
}
可以看到Router接口继承Comparable接口,子类需要实现compareTo方法来处理排序,再看看三个抽象方法,getUrl()获取的是路由url,
route(List<Invoker> invokers, URL url, Invocation invocation)方法是进行路由规则筛选的方法,通过路由规则对invokers(服务提供者列表)进行筛选,选择一个或者一堆合适的invoker。getPriority()这个方法是获取优先级的方法,用来排序使用的。
1.2 路由实现
在dubbo v2.6.x及以前的版本,支持条件路由规则 ,脚本路由规则,标签路由规则
在condition包下面分别是条件路由工厂ConditionRouterFactory与条件路由ConditionRouter。
在file包下面是FileRouterFactory,这个文件工厂其实是根据路由文件的尾追来判断使用哪种路由规则的。
在script包下面分别是脚本路由工厂ScriptRouterFactory 与脚本路由ScriptRouter。
在tag包下面是标签路由TagRouter,这个没有工厂,dubbo默认是new TagRouter()对象塞到routers中的。
下面还有两个类AbstractRouter 与MockInvokersSelector ,AbstractRouter 类是一个抽象类,实现Router接口,重写了compareTo 排序排序方法与getUrl()方法getPriority(),该抽象类主要还是做了排序这块,下面的路由规则都继承它。MockInvokersSelector这个路由实现主要是做mock用的。
路由继承关系图:
路由工厂继承关系图:
接下来我们看下路由的添加与修改。
2. 路由添加与修改
我们在《深度解析dubbo集群之Directory》一文中讲解AbstractDirectory抽象类的时候其实讲解了添加router方法与使用路由过滤方法,在这里我们在看下
2.1添加路由
AbstractDirectory抽象类中有一个setRouters(List routers) 方法就是添加路由的,我们看下它的源码:
/**
* 设置路由规则
* @param routers
*/
protected void setRouters(List<Router> routers) {
// copy list
routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
// append url router 获取router参数值
String routerkey = url.getParameter(Constants.ROUTER_KEY);
if (routerkey != null && routerkey.length() > 0) {// 如果router参数值不是空的话, 获取对应的工厂
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
routers.add(routerFactory.getRouter(url));
}
// append mock invoker selector 添加几个系统自带的路由处理
routers.add(new MockInvokersSelector());// mock router
routers.add(new TagRouter()); // tag router
//路由排序
Collections.sort(routers);
this.routers = routers;
}
可以看到是将传过来的路由集合放到新创建的集合中,然后获取用户设置router参数值(这个router其实就是设置使用哪种路由规则),如果router参数值不是空,使用路由工厂获取对应的路由对象,然后放到集合中,我们看到后面 dubbo手动new了MockInvokersSelector对象与TagRouter路由对象放到集合中了,最后就是排序了。
需要说明的是,调用setRouters 设置路由的地方只有两个,一个是AbstractDirectory的构造方法中调用了setRouters (这个在dubbo当前版本没有调用,只是传入了null),再一个地方就是AbstractDirectory子类RegistryDirectory 的notify方法中(向注册中心订阅了,然后当注册中心变动的时候,就会通知调用该方法)
2.2 路由修改
AbstractDirectory的子类RegistryDirectory 实现了NotifyListener 接口,并且向注册中心订阅了consumerUrl,当注册中心变动的时候,就会调用notify方法。在RegistryDirectory实现的notify中,会对url的类型进行分类处理,protocol是route或者category是routers的url会被认为是路由url,然后调用 toRouters(routerUrls);方法将找出来的routerUrl们转成route
//处理路由规则url集合
// 当url 有变化的时候,重新设置router
if (routerUrls != null && !routerUrls.isEmpty()) {
List<Router> routers = toRouters(routerUrls);//将routerUrl转换成url
if (routers != null) { // null - do nothing
setRouters(routers);
}
}
我们接着看下这个toRouters(routerUrls);方法
/**
* 将urls 转换成 routers
* @param urls
* @return null : no routers ,do nothing
* else :routers list
*/
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) {
//如果protocol是empty 就跳过
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
// 获取router
String routerType = url.getParameter(Constants.ROUTER_KEY);
if (routerType != null && routerType.length() > 0) {
url = url.setProtocol(routerType);// 设置route 协议
}
try {
//使用dubbo spi 获得Router
Router router = routerFactory.getRouter(url);
if (!routers.contains(router))
routers.add(router);//如果list没有这个router, 添加
} catch (Throwable t) {
logger.error("convert router url to router error, url: " + url, t);
}
}
}
return routers;
}
我们可以看到这个toRouters方法比较简单,遍历url,然后protocol是empty的就跳过,获取router属性值,也就是获取路由规则的类型,然后使用路由工厂获取对应类型的路由对象,如果路由没有存在集合中,就是设置到集合中,到这里我们的toRouters方法就解析完成了。
我们再回过头来看下notify对路由的操作,toRouters 方法完成后返回一个路由集合,接着又调用setRouters(List routers)方法将路由集合设置给AbstractDirectory对象的routers 成员。
2.3 使用路由过滤
在AbstractDirectory类使用list方法获取服务提供者们的时候,并不是有多少服务提供者就返回多少,而是需要经过路由规则过滤。我们来看看list方法。
/**
* 根据invocation信息获取invoker列表
* @param invocation 调用信息
* @return
* @throws RpcException
*/
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) { //判断是否销毁
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
// 调用子类实现
List<Invoker<T>> invokers = doList(invocation);
List<Router> localRouters = this.routers; // local reference
// 根据路由规则筛选 router
if (localRouters != null && !localRouters.isEmpty()) {
for (Router router : localRouters) {// 根据路由将 invoker过滤一遍
try {
// runtime 这个参数表示是否在运行时进行路由过滤,缺省是false,这玩意是true的话,每次获取服务提供者列表的时候都要
// 过滤,这是很耗费性能的,其实在从注册中心通知过来的服务提供者url,进行转成method与服务提供者列表对应关系的时候
//就已经对服务提供者进行了路由规则的处理。
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
先是调用子类实现的doList方法获取了所有的服务提供者们,接着就是获取路由集合routers,遍历路由,然后判断runtime 是否需要调用的时候过滤,如果需要就要使用路由过滤服务提供者们,这里只是使用路由过滤的地方之一。
我们接着看下另一个使用路由过滤的地方—RegistryDirectory 的notify方法,关于这个notify方法我们上面讲解过它大体是干嘛的,在将url转成invoker的refreshInvoker方法中,有一个处理method与invoker对应关系toMethodInvokers 方法中,对每一个方法对应的invoker集合进行了路由筛选,我们看下这部分的代码
遍历接口的方法,在map中找不到对应的invoker集合,就设置成newInvokersList这个集合,可以看到是被route方法处理过,如果能找到对应服务提供者集合,对服务提供者集合进行route处理,然后再塞到map中。
看下这个route方法:
/**
* 使用route 过滤 invokers
* @param invokers
* @param method
* @return
*/
private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {
Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
List<Router> routers = getRouters();///获取routers
if (routers != null) {
for (Router router : routers) {
// If router's url not null and is not route by runtime,we filter invokers here router url不是null && runtime属性是false 的时候过滤
if (router.getUrl() != null && !router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
}
}
return invokers;
}
可以看到是遍历router集合,进行route处理。
3.AbstractRouter源码解析
上面我们说过AbstractRouter 抽象类主要是实现类排序compareTo方法,我们看下AbstractRouter的源码:
/**
* 主要是处理 Router的排序问题
*/
public abstract class AbstractRouter implements Router {
protected URL url;// 配置路由url
protected int priority; // 优先级 ,按照从小到大的顺序
@Override
public URL getUrl() {
return url;
}
@Override
public int compareTo(Router o) {
return (this.getPriority() < o.getPriority()) ? -1 : ((this.getPriority() == o.getPriority()) ? 0 : 1);
}
// 获取优先级
public int getPriority() {
return priority;
}
}
可以看到,定义了两个成员,一个是优先级数值priority, 一个是配置路由的url,我们看下compareTo比较路由优先级的方法,这个是按照从小到大顺序排序的。