Dubbo 源码分析 - 集群容错之 Directory

 

1. 简介

前面文章分析了服务的导出与引用过程,从本篇文章开始,我将开始分析 Dubbo 集群容错方面的源码。这部分源码包含四个部分,分别是服务目录 Directory、服务路由 Router、集群 Cluster 和负载均衡 LoadBalance。这几个部分的源码逻辑比较独立,我会分四篇文章进行分析。本篇文章作为集群容错的开篇文章,将和大家一起分析服务目录相关的源码。在进行深入分析之前,我们先来了解一下服务目录是什么。服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。在一个服务集群中,服务提供者数量并不是一成不变的,如果集群中新增了一台机器,相应地在服务目录中就要新增一条服务提供者记录。或者,如果服务提供者的配置修改了,服务目录中的记录也要做相应的更新。如果这样说,服务目录和注册中心的功能不就雷同了吗。确实如此,这里这么说是为了方便大家理解。实际上服务目录在获取注册中心的服务配置信息后,会为每条配置信息生成一个 Invoker 对象,并把这个 Invoker 对象存储起来,这个 Invoker 才是服务目录最终持有的对象。Invoker 有什么用呢?看名字就知道了,这是一个具有远程调用功能的对象。讲到这大家应该知道了什么是服务目录了,它可以看做是 Invoker 集合,且这个集合中的元素会随注册中心的变化而进行动态调整。

好了,关于服务目录这里就先介绍这些,大家先有个大致印象即可。接下来我们通过继承体系图来了解一下服务目录的家族成员都有哪些。

2. 继承体系

服务目录目前内置的实现有两个,分别为 StaticDirectory 和 RegistryDirectory,它们均是 AbstractDirectory 的子类。AbstractDirectory 实现了 Directory 接口,这个接口包含了一个重要的方法定义,即 list(Invocation),用于列举 Invoker。下面我们来看一下他们的继承体系图。

如上,Directory 继承自 Node 接口,Node 这个接口继承者比较多,像 Registry、Monitor、Invoker 等继承了这个接口。这个接口包含了一个获取配置信息的方法 getUrl,实现该接口的类可以向外提供配置信息。另外,大家注意看 RegistryDirectory 实现了 NotifyListener 接口,当注册中心节点信息发生变化后,RegistryDirectory 可以通过此接口方法得到变更信息,并根据变更信息动态调整内部 Invoker 列表。

现在大家对服务目录的继承体系应该比较清楚了,下面我们深入到源码中,探索服务目录是如何实现的。

3. 源码分析

本章我将分析 AbstractDirectory 和它两个子类的源码。这里之所以要分析 AbstractDirectory,而不是直接分析子类是有一定原因的。AbstractDirectory 封装了 Invoker 列举流程,具体的列举逻辑则由子类实现,这是典型的模板模式。所以,接下来我们先来看一下 AbstractDirectory 的源码。

public List<Invoker<T>> list(Invocation invocation) throws RpcException {     if (destroyed) {         throw new RpcException("Directory already destroyed...");     }          // 调用 doList 方法列举 Invoker,这里的 doList 是模板方法,由子类实现     List<Invoker<T>> invokers = doList(invocation);          // 获取路由器     List<Router> localRouters = this.routers;     if (localRouters != null && !localRouters.isEmpty()) {         for (Router router : localRouters) {             try {                 // 获取 runtime 参数,并根据参数决定是否进行路由                 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: ...");             }         }     }     return invokers; }  // 模板方法,由子类实现 protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;

上面就是 AbstractDirectory 的 list 方法源码,这个方法封装了 Invoker 的列举过程。如下:

  1. 调用 doList 获取 Invoker 列表
  2. 根据 Router 的 getUrl 返回值为空与否,以及 runtime 参数决定是否进行服务路由

以上步骤中,doList 是模板方法,需由子类实现。Router 的 runtime 参数这里简单说明一下,这个参数决定了是否在每次调用服务时都执行路由规则。如果 runtime 为 true,那么每次调用服务前,都需要进行服务路由。这个对性能造成影响,慎重配置。关于该参数更详细的说明,请参考public class StaticDirectory<T> extends AbstractDirectory<T> { // Invoker 列表 private final List<Invoker<T>> invokers; // 省略构造方法 @Override public Class<T> getInterface() { // 获取接口类 return invokers.get(0).getInterface(); } // 检测服务目录是否可用 @Override public boolean isAvailable() { if (isDestroyed()) { return false; } for (Invoker<T> invoker : invokers) { if (invoker.isAvailable()) { // 只要有一个 Invoker 是可用的,就任务当前目录是可用的 return true; } } return false; } @Override public void destroy() {

50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信