3.Dubbo注册中心架构与源码

2021-04-07  本文已影响0人  方雲

3.1 注册中心概述

在Dubbo微服务体系中,注册中心是其核心组件之一。Dubbo通过注册中心实现了分布式环境中各服务之间的注册与发现,是各个分布式节点之间的纽带。
Dubbo的注册中心源码在模块dubbo-registry中,里面包含五个子模块:

3.1.1 工作流程

3.1.2 Zookeeper原理概述

Zookeeper是树形结构的注册中心,节点分为持久节点、持久顺序节点、临时节点和临时顺序节点4种。

3.1.3 ZooKeeper的实现

  1. 发布的实现:
    Provider和Consumer都需要把自己注册到注册中心。Provider的注册是为了让Consumer感知到服务的存在,从而发起远程调用;也让服务治理中心感知到有新的provider上线。Consumer的注册是为了让服务治理中心发现自己。ZooKeeper发布代码非常简单,只是调用了ZooKeeper的客户端库在注册中心上创建一个目录:
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));

取消发布也很简单,只是把ZooKeeper注册中心上对应的路径删除:

zkClient.delete(toUrlPath(url));
  1. 订阅的实现
    订阅通常有pull和push两种方式,一种是客户端定时轮询注册中心拉取配置,另一种是注册中心主动推送数据给客户端。两种方式各有利弊,目前Dubbo采用的是第一次启动拉取,后续接收事件重新拉取数据。
    在服务暴露时,provider会订阅configurators用于监听动态配置,在消费端启动时,consumer会订阅providers、routers和configurators这三个目录,分别对应服务提供者、路由和动态配置变更通知。
    Dubbo在dubbo-remoting-zookeeper模块中实现了ZooKeeper客户端的统一封装,定义了统一的Client API,并用两种不同的ZooKeeper开源客户端库实现了这个接口: Apache Curator和zkClient。用户可以使用<dubbo:registry>的client属性切换实现,默认使用的是curator。
    ZooKeeper注册中心采用的是“事件通知”+“客户端拉取”的方式,客户端第一次连接上注册中心时,会获取对应目录的全量数据,并在订阅的节点上注册一个watcher,客户端与注册中心之间保持TCP长连接,后续每个节点有任何数据变化,注册中心都会根据watcher的回调主动通知客户端(事件通知),接到通知后,会把对应节点下的全量数据都拉取过来,这一点在NotifyListener#notify(List<URL> urls)接口上就有约束的注释说明。但全量拉取有一个局限,当微服务节点很多时会对网络造成很大的压力。

什么操作会被认为是ZooKeeper的事务操作
客户端任何新增、删除、修改、会话创建和失效操作,都会被认为是事务操作,都会由ZooKeeper中的leader执行。及时客户端连接的是非leader节点,请求也会被转发给leader执行,以此来保证所有事务的全剧时序性,由于每个节点都有一个版本号,因此可以通过CAS操作比较版本号来保证该节点数据操作的原子性

客户端第一次连上注册中心,订阅时会获取全量数据,后续通过监听器事件进行更新。服务治理中心会处理所有service层的订阅,service被设置成特殊值*。此外,服务治理中心除了订阅当前节点,还会订阅这个节点下的所有子节点,核心代码来自ZookeeperRegistry:


image.png

接下来是普通消费者的订阅逻辑,首先根据URL的类别得到一组需要订阅的路径,如果类别是*,则会订阅四种类型的路径(providers、routers、consuemrs、configurators),否则只订阅providers。


image.png

3.2 注册中心缓存机制
如果每次远程调用都要先从注册中心获取一次可调用的服务列表,则会让注册中心承受巨大的流量压力。另外,每次额外的网络请求也会让整个系统性能下降,因此Dubbo的注册中心实现了通用的缓存机制,在抽象类AbstractRegistry中实现。
消费者或服务治理中心获取注册信息后会做本地缓存。内存中会有一份,保存在Properties对象里,磁盘上也会持久化一份文件,通过file对象引用。


image.png

内存中的缓存notified是ConcurrentHashMap里面又嵌套了一个Map,外层Map的key是消费者的URL,内层的Map的Key是分类,包含providers、consumers、routers、configurators四种。value则是对应的服务列表,对于没有服务提供者提供的URL,它会以特殊的empty://前缀开头。

翻看了下源码,个人理解是,properties和map都是内存中的缓存,properties主要是为了方便与file的交互进行直接的读入和写出,map才是运行时主要使用的。启动时,从文件加载到的properties并没有直接写入map中,而是在notify方法中才对properties和map进行同时写入的,也就是说map的数据永远来源于注册中心,刚启动的时候是空的,只有当注册中心不可用的情况下才会去使用properties。(3.2.1证明这点理解是正确的)

3.2.1 缓存的加载

在服务初始化时,AbstractRegistry构造函数里会从本地磁盘文件中把持久化的注册数据读到Properties对象里,并加载到内存缓存中。


image.png

Properties保存了所有服务提供者的URL,使用URL#serviceKey()作为key,提供者列表、路由规则列表、配置规则列表等作为value。由于value是列表,所以用空格隔开多个。还有一个特殊的key.registies,保存了所有的注册中心地址。如果应用在启动过程中,注册中心无法连接或者宕机,Dubbo会自动通过本地缓存加载Invokers。

3.2.2 缓存的保存与更新

缓存的保存有同步和异步两种方式。异步会使用线程池异步保存,如果线程在执行过程中出现异常,会再次调用线程池不断重试。


image.png

AbstractRegistry#notify方法中封装了更新内存缓存和文件缓存的逻辑。当客户端第一次订阅全量数据,或者后续由于订阅得到新数据,都会调用notify方法来保存。

3.3 重试机制

我们知道FailbackRegistry继承了AbstractRegistry,并在此基础上增加了失败重试机制。ZooKeeperRegistry直接继承FailbackRegistry接口直接使用重试机制。
FailbackRegistry抽象类定义了一个ScheduledExecutorService,每隔固定时间(默认5秒)就调用FailbackRegistry#retry()方法,另外还通过5个集合来记录:


image.png

retry方法会把这5个集合分别进行遍历和重试,重试成功就移除。FailbackRegistry实现了subscribe、unsubscribe等通用方法,里面调用了抽象的模板方法,将由子类来实现,通过这种模板方法的调用,如果捕获到异常,就会把URL添加到对应的重试集合中,以供定时器去重试。

上一篇下一篇

猜你喜欢

热点阅读