本文基于 Dubbo 2.6.1 版本,望知悉。
1. 概述
前置阅读文章:
😈 在《注册中心(一)之抽象 API》 中,我们分享的那是相当抽象。因此,在本文中,我们会分享 Dubbo 使用 Zookeeper 作为注册中心的代码,同时也会分享服务暴露和引用时,对注册中心的使用。
下面,我们先来看下 《Dubbo 用户指南 —— zookeeper 注册中心》 文档,内容如下:
流程说明:
服务提供者启动时: 向
/dubbo/com.foo.BarService/providers目录下写入自己的 URL 地址服务消费者启动时: 订阅
/dubbo/com.foo.BarService/providers目录下的提供者 URL 地址。并向/dubbo/com.foo.BarService/consumers目录下写入自己的 URL 地址监控中心启动时: 订阅
/dubbo/com.foo.BarService目录下的所有提供者和消费者 URL 地址。
在图中,我们可以看到 Zookeeper 的节点层级,自上而下是:
- Root 层:根目录,可通过
<dubbo:registry group="dubbo" />的"group"设置 Zookeeper 的根节点,缺省使用"dubbo"。 - Service 层:服务接口全名。
- Type 层:分类。目前除了我们在图中看到的
"providers"( 服务提供者列表 )"consumers"( 服务消费者列表 ) 外,还有"routes"( 路由规则列表 ) 和"configurations"( 配置规则列表 )。 - URL 层:URL ,根据不同 Type 目录,下面可以是服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。
- 实际上 URL 上带有
"category"参数,已经能判断每个 URL 的分类,但是 Zookeeper 是基于节点目录订阅的,所以增加了 Type 层。
- Root 层:根目录,可通过
实际上,服务消费者启动后,不仅仅订阅了
"providers"分类,也订阅了"routes""configurations"分类。
2. ZookeeperRegistryFactory
com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory ,实现 AbstractRegistryFactory 抽象类,Zookeeper Registry 工厂。代码如下:
public class ZookeeperRegistryFactory extends AbstractRegistryFactory { |
3. ZookeeperRegistry
com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry ,实现 FailbackRegistry 抽象类,Zookeeper Registry 。
3.1 属性 + 构造方法
1: /** |
root属性,Zookeeper 根节点,即首图的 Root 层。anyServices属性,Service 接口接口全名集合。该属性适可用于监控中心,订阅整个 Service 层。因为,Service 层是动态的,可以有不断有新的 Service 服务发布(注意,不是服务实例)。在#doSubscribe(url, notifyListener)方法中,会更容易理解。zkListeners属性,监听器集合,建立 NotifyListener 和 ChildListener 的映射关系。zkClient属性,Zookeeper 客户端。- 构造方法
- 第 28 至 31 行:设置注册中心的 URL 。
- 第 32 至 37 行:设置在 Zookeeper 的根节点,缺省使用
DEFAULT_ROOT。 - 第 39 行:调用
ZookeeperTransporter#connect(url)方法,基于 Dubbo SPI Adaptive 机制,根据url参数,加载对应的 ZookeeperTransporter 实现类,创建对应的 ZookeeperClient 实现类的对应。 - 第 41 至 51 行:添加 StateListener 对象到 ZookeeperClient 对象中。该监听器,在重连时,在第 45 行的代码,调用
#recover()方法,进行恢复逻辑,重新发起注册和订阅。
3.2 doRegister
1: |
- 第 4 行:调用
#toUrlPath(url)方法,获得 URL 的路径。 - 第 4 行:
url.parameters.dynamic,是否动态数据。若为 false ,该数据为持久数据,当注册方退出时,数据依然保存在注册中心。 - 第 4 行:调用
ZookeeperClient#create(url, ephemeral)方法,创建 URL 节点,即我们在首图看到的 URL 层。
3.2.1 toUrlPath
/** |
3.2.2 toCategoryPath
/** |
3.2.3 toServicePath
/** |
3.2.4 toRootDir
/** |
3.3 doUnregister
|
3.4 doSubscribe
1: |
- 整个方法分成两部分,分别:
- ============ 第二部分【第 44 至 78 行】 ============
- 处理指定 Service 层的发起订阅,例如服务消费者的订阅。
- 第 47 行:子节点数据数组,即 Service 层下的所有 URL 。
- 第 49 行:循环分类数组。其中,调用
#toCategoriesPath(url)方法,获得 分类数组。 - 第 51 至 55 行:获得订阅的
url对应的监听器集合。 - 第 56 至 66 行:获得
listener( NotifyListener ) 对应的 ChildListener 对象。在 URL 层发生变更时,会调用NotifyListener#notify(url, listener, currentChilds)方法,回调 NotifyListener 的逻辑。酱紫,如果 Service 下增加新的服务提供者实例( 新的 URL ),服务消费者可创建新的 Invoker 对象,用于调用该服务提供者。 - 第 68 行:创建 Type 节点。该节点为持久节点。
- 第 70 行:向 Zookeeper 的 Path 节点,发起订阅。
- 第 72 至 74 行:添加到
urls中。 - 第 77 行:首次全量数据获取完成时,调用
NotifyListener#notify(url, listener, currentChilds)方法,回调 NotifyListener 的逻辑。酱紫,服务消费者可创建所有的 Invoker 对象,用于调用服务提供者们。 - 🙂 回看【第 77 行】和【第 62 行】,全量 + 增量,仔细理解下。
- ============ 第一部分【第 5 至 43 行】 ============
- 处理所有 Service 层的发起订阅,例如监控中心的订阅
- 第 8 至 12 行:获得订阅的
url对应的监听器集合。 - 第 13 至 30 行:获得
listener( NotifyListener ) 对应的 ChildListener 对象。在 Service 层发生变更时,若是新增 Service 接口全名时(即新增服务),调用#subscribe(url, listener)方法,发起该 Service 层的订阅(【第 45 至 78 行】的逻辑)。是否是新增的服务,通过anyServices属性来判断。 - 第 32 行:创建 Service 节点。该节点为持久节点。
- 第 34 行:向 Zookeeper 的 Service 节点,发起订阅。
- 第 36 至 43 行:首次全量数据获取完成时,循环 Service 接口全名数组,调用
#subscribe(url, listener)方法,发起该 Service 层的订阅(【第 45 至 78 行】的逻辑)。
友情提示:如果觉得比较绕,或者笔者讲的不清晰,胖友可以进行调试理解。
3.4.1 toCategoriesPath
/** |
3.4.2 toUrlsWithEmpty
/** |
#toUrlsWithoutEmpty()方法,代码如下:/**
* 获得 providers 中,和 consumer 匹配的 URL 数组
*
* @param consumer 用于匹配 URL
* @param providers 被匹配的 URL 的字符串
* @return 匹配的 URL 数组
*/
private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
List<URL> urls = new ArrayList<URL>();
if (providers != null && !providers.isEmpty()) {
for (String provider : providers) {
provider = URL.decode(provider);
if (provider.contains("://")) { // 是 url
URL url = URL.valueOf(provider); // 将字符串转化成 URL
if (UrlUtils.isMatch(consumer, url)) { // 匹配
urls.add(url);
}
}
}
}
return urls;
}
3.5 doUnsubscribe
|
3.6 lookup
/** |
3.7 isAvailable
|
3.8 destroy
|
4. 调用
4.1 服务提供者
回头看 《精尽 Dubbo 源码分析 —— 服务暴露(二)之远程暴露(Dubbo)》 的 「3.2.2 export」 小节,我们可以看到:
第 14 行:调用
#register(registryUrl, registedProviderUrl)方法,向注册中心注册服务提供者(自己)。代码如下:public void register(URL registryUrl, URL registedProviderUrl) {
Registry registry = registryFactory.getRegistry(registryUrl);
registry.register(registedProviderUrl);
}
4.2 服务消费者
回头看 《精尽 Dubbo 源码分析 —— 服务引用(二)之远程引用(Dubbo)》 的 「3.2.2 doRefer 小节,我们可以看到:
- 第 20 至 25 行:调用
RegistryService#register(url)方法,向注册中心注册自己(服务消费者)。 - 第 35 行:调用
Directory#subscribe(url)方法,向注册中心订阅服务提供者 + 路由规则 + 配置规则。- 在该方法中,会循环获得到的服务体用这列表,调用
Protocol#refer(type, url)方法,创建每个调用服务的 Invoker 对象。
- 在该方法中,会循环获得到的服务体用这列表,调用
666. 彩蛋
嘿嘿,写完 Zookeeper 作为注册中心是否清晰了一些?!