本文基于 Dubbo 2.6.1 版本,望知悉。
1. 概述
在 《精尽 Dubbo 源码分析 —— 项目结构一览》「3.5 dubbo-registry」 中,对 dubbo-registry 注册中心这个大模块做了大体的介绍。那么从本文开始,分享注册中心的代码实现。
本文分享 dubbo-registry-api 模块,注册中心的抽象 API ,类结构如下图:
😈 整体比较易懂,笔者在这里先不介绍,胖友可以看完本文,回过头看看,自己是不是理解了?!
下面,我们按照从左到右的顺序,逐个分享。
2. RegistryFactory
com.alibaba.dubbo.registry.RegistryFactory ,注册中心工厂接口,代码如下:
("dubbo") |
- RegistryFactory 是一个 Dubbo SPI 拓展接口。
#getRegistry(url)方法,获得注册中心 Registry 对象。- 注意方法上注释的处理契约。
@Adaptive({"protocol"})注解,Dubbo SPI 会自动实现 RegistryFactory$Adaptive 类,根据url.protocol获得对应的 RegistryFactory 实现类。例如,url.protocol = zookeeper时,获得 ZookeeperRegistryFactory 实现类。
2.1 AbstractRegistryFactory
com.alibaba.dubbo.registry.support.AbstractRegistryFactory ,实现 RegistryFactory 接口,RegistryFactory 抽象类,实现了 Registry 的容器管理。
2.1.1 属性
// The lock for the acquisition process of the registry |
REGISTRIES静态属性,Registry 集合。LOCK静态属性,锁,用于#destroyAll()和#getRegistry(url)方法,对REGISTRIES访问的竞争。
2.1.2 createRegistry
#createRegistry(url) 抽象方法,创建 Registry 对象。代码如下:
/** |
子类实现该方法,创建其对应的 Registry 实现类。例如,ZookeeperRegistryFactory 的该方法,创建 ZookeeperRegistry 对象。
2.1.3 getRegistry
#getRegistry(url) 实现方法,获得注册中心 Registry 对象。优先从缓存中获取,否则进行创建。
- 🙂 实现比较易懂,点击链接查看,有代码注释。
2.1.4 destroyAll
#destroyAll() 方法,销毁所有 Registry 对象。
- 🙂 实现比较易懂,点击链接查看,有代码注释。
3. RegistryService
com.alibaba.dubbo.registry.RegistryService ,注册中心服务接口,定义了注册、订阅、查询三种操作方法,如下:
#register(url)方法,注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。#unregister(url)方法,取消注册。
#subscribe(url, NotifyListener)方法,订阅符合条件的已注册数据,当有注册数据变更时自动推送。#lookup(url)方法,查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
ps:注意方法上注释的处理契约。
3.1 Registry
com.alibaba.dubbo.registry.Registry ,注册中心接口。Registry 继承了:
- RegistryService 接口,拥有拥有注册、订阅、查询三种操作方法。
com.alibaba.dubbo.common.Node接口,拥有节点相关的方法。
3.2 AbstractRegistry
com.alibaba.dubbo.registry.support.AbstractRegistry ,实现 Registry 接口,Registry 抽象类,实现了如下方法:
- 通用的注册、订阅、查询、通知等方法。
- 持久化注册数据到文件,以 properties 格式存储。应用于,重启时,无法从注册中心加载服务提供者列表等信息时,从该文件中读取。
3.2.1 属性
1: // URL地址分隔符,用于文件缓存中,服务提供者URL分隔 |
file属性,见代码注释。properties属性,见代码注释。syncSaveFile属性,properties发生变更时候,是同步还是异步写入file。registryCacheExecutor属性,见代码注释。lastCacheChanged属性,见代码注释。- 因为每次写入
file是全量,而不是增量写入,通过版本号,避免老版本覆盖新版本。
- 因为每次写入
registered属性,见代码注释。subscribed属性,见代码注释。notified属性,见代码注释。- 从数据上,和
properties比较相似。笔者认为有两点差异:1)数据格式上,notified根据分类做了聚合;2)不从file中读取,都是从注册中心读取的数据。
- 从数据上,和
registryUrl属性,见代码注释。destroyed属性,见代码注释。- 构造方法,见代码注释。
- 第 87 行:调用
#loadProperties()方法,加载本地磁盘缓存文件到内存缓存。- 🙂 代码比较简单,点击链接查看。
- 第 89 行:// 【TODO 8020】为什么构造方法,要通知,连监听器都没注册
- 第 87 行:调用
3.2.2 register && unregister
#register(url)- 从实现上,我们可以看出,并未向注册中心发起注册,仅仅是添加到
registered中,进行状态的维护。实际上,真正的实现在 FailbackRegistry 类中。
- 从实现上,我们可以看出,并未向注册中心发起注册,仅仅是添加到
#unregister(url)- 和
#register(url)的处理方式相同。
- 和
3.2.3 subscribe && unsubscribe
#subscribe(url, listener)- 和
#register(url)的处理方式相同。
- 和
#unsubscribe(url, listener)- 和
#register(url)的处理方式相同。
- 和
3.2.4 notify
#notify(url, listener, urls) 方法,通知监听器,URL 变化结果。这里我们有两点要注意下:
- 第一,向注册中心发起订阅后,会获取到全量数据,此时会被调用
#notify(...)方法,即 Registry 获取到了全量数据。 - 第二,每次注册中心发生变更时,会调用
#notify(...)方法,虽然变化是增量,调用这个方法的调用方,已经进行处理,传入的urls依然是全量的。
代码如下:
1: /** |
- 第 25 至 37 行:将
urls按照url.parameter.category分类,添加到集合result中。- 第 28 行:TODO 芋艿
- 这里有一点要注意,每次传入的
urls的“全量”,指的是至少要是一个分类的全量,而不一定是全部数据。
- 第 41 至 46 行:获得消费者 URL 对应的在
notified中的数据。 - 第 47 至 58 行:按照分类,循环处理通知的 URL 变化结果(全量数据)。
- 第 51 至 53 行:将
result覆盖到notified中。这里又有一点需要注意,当某个分类的数据为空时,会依然有urls。其中urls[0].protocol = empty,通过这样的方式,处理所有服务提供者为空的情况。 - 第 55 行:调用
#saveProperties(url)方法,保存到文件。- 🙂 代码比较简单,点击链接查看。
- 第 57 行:调用
NotifyListener#notify(urls)方法,通知监听器处理。例如,有新的服务提供者启动时,被通知,创建新的 Invoker 对象。
- 第 51 至 53 行:将
3.2.5 recover
#recover()- 和
#register(url)的处理方式相同。
- 和
在注册中心断开,重连成功,调用 #recover() 方法,进行恢复注册和订阅。
3.2.6 destroy
#destroy()- 和
#register(url)的处理方式相同。
- 和
在 JVM 关闭时,调用 #destroy() 方法,进行取消注册和订阅。
3.3 FailbackRegistry
com.alibaba.dubbo.registry.support.FailbackRegistry ,实现 AbstractRegistry 抽象类,支持失败重试的 Registry 抽象类。
在上文中的代码中,我们可以看到,AbstractRegistry 进行的注册、订阅等操作,更多的是修改状态,而无和注册中心实际的操作。FailbackRegistry 在 AbstractRegistry 的基础上,实现了和注册中心实际的操作,并且支持失败重试的特性。
3.3.1 属性
1: /** |
retryExecutor属性,见代码注释。retryFuture属性,见代码注释。- 第 41 至 51 行,在构造方法中创建该定时器,在其
#run()方法中,会调用#retry()方法,进行重试。
- 第 41 至 51 行,在构造方法中创建该定时器,在其
failedXXX属性,见代码注释。- 每种操作都有一个记录失败的集合。
destroyed属性,见代码注释。
3.3.2 register && unregister
代码比较易懂,点击链接查看。
3.3.3 subscribe && unsubscribe
代码比较易懂,点击链接查看。
3.3.4 notify
代码比较易懂,点击链接查看。
3.3.5 recover
#recover()方法,完全覆盖父类方法( 即不像前面几个方法,会调用父类的方法 ),将需要注册和订阅的 URL 添加到failedRegisteredfailedSubscribed属性中。这样,在#retry()方法中,会重试进行连接。
代码比较易懂,点击链接查看。
3.3.6 retry
#retry()方法,遍历五个failedXXX属性,重试对应的操作。
代码比较易懂,点击链接查看。
3.3.7 destroy
#destroy()方法,取消注册和订阅,并关闭定时器。
代码比较易懂,点击链接查看。
4. NotifyListener
com.alibaba.dubbo.registry.NotifyListener ,通知监听器。当收到服务变更通知时触发,代码如下:
public interface NotifyListener { |
- 注意看方法上的注释,特别是全量、分类、为空、顺序。
NotifyListener 的子类如下图:
5. ProviderConsumerRegTable
com.alibaba.dubbo.registry.support.ProviderConsumerRegTable ,服务提供者和消费者注册表,存储 JVM 进程内自己的服务提供者和消费者的 Invoker 。
该信息用于 Dubbo QOS 使用,例如将 JVM 进程中,自己的服务提供者下线,又或者查询自己的服务提供者和消费者列表。
- 《Dubbo 用户指南 —— 在线运维命令 - QOS》
- 后续会有文章分享 QOS ,本文不多啰嗦。
代码如下:
public class ProviderConsumerRegTable { |
- 如下方法,已经添加代码注释,胖友点击查看。
- 服务提供者
#registerProvider(invoker, registryUrl, providerUrl)静态方法,注册 Provider Invoker 。#getProviderInvoker(serviceUniqueName)静态静态,获得指定服务键的 Provider Invoker 集合。#getProviderWrapper(invoker)静态方法,获得服务提供者对应的 Invoker Wrapper 对象。
- 服务消费者
#registerConsumer(invoker, registryUrl, consumerUrl, registryDirectory)静态方法,注册 Consumer Invoker 。#getConsumerInvoker(serviceUniqueName)静态方法,获得指定服务键的 Consumer Invoker 集合。
5.1 ProviderInvokerWrapper
com.alibaba.dubbo.registry.support.ProviderInvokerWrapper ,实现 Invoker 接口,服务提供者 Invoker Wrapper ,代码如下:
/** |
- 相比纯粹的 Invoker 对象,又多了运维命令需要的属性。例如
isReg状态属性,可以在使用下线服务命令后,标记为false。想提前深入了解的胖友,可以看下com.alibaba.dubbo.qos.command.impl.Offline和com.alibaba.dubbo.qos.command.impl.Online类。
5.2 ConsumerInvokerWrapper
com.alibaba.dubbo.registry.support.ConsumerInvokerWrapper ,实现 Invoker 接口,服务消费者 Invoker Wrapper ,代码如下:
/** |
- 相比纯粹的 Invoker 对象,又多了运维命令需要的属性。例如
registryDirectory属性,可以在使用列出消费者和提供者命令后,输出可消费者可调用的服务提供者数量 。想提前深入了解的胖友,可以看下com.alibaba.dubbo.qos.command.impl.Ls类。
5. integration
不同于上面我们看到的代码,integration 包下是对其他 Dubbo 模块的集成:
- RegistryProtocol ,对
dubbo-rpc-api的依赖集成。 - RegistryDirectory ,对
dubbo-cluster的依赖集成。
考虑到超出了本文的范畴,后面涉及到时,单独分享。
666. 彩蛋
对注册中心的使用,不熟悉的胖友,可能理解起来会有点懵。嘿嘿,仿佛为自己写的差,找了一个理由。哈哈哈。
下一篇,我们来结合 Zookeeper ,进一步理解。
另外,胖友也可以多多调试噢。