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

3.1 注册中心概述

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

  • dubbo-registry-api 注册中心的api和抽象实现类
  • dubbo-registry-zookeeper 使用zookeeper作为注册中心
  • dubbo-registry-redis 使用redis作为注册中心
  • dubbo-registry-default 基于内存的默认实现
  • dubbo-registry-multicast multicast模式的服务注册与发现
    生成环境推荐使用zookeeper。

3.1.1 工作流程

  • Provider启动时,会向注册中心写入自己的元数据信息,同时会订阅Config元数据信息。
  • Consumer启动时,也会向注册中心写入自己的元数据信息,并订阅Provider、Router和Config元数据信息。
  • 服务治理中心(dubbo-admin)启动时,会同时订阅所有Consumer、Provider、Router和Config元数据信息。
  • 当有Provider离开或有新的Provider加入时,注册中心Provider目录会发生变化,变化信息会动态通知给Consumer和服务治理中心。
  • 当Consumer发起服务调用,会异步将调用和统计信息上报给监控中心(dubbo-monitor-simple)。

3.1.2 Zookeeper原理概述

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

  • 持久节点:服务注册后保证不会丢失,注册中心重启后也会存在。
  • 持久顺序节点:在持久节点特性的基础上增加了节点先后顺序的能力。
  • 临时节点:服务注册后连接丢失或session超时,注册的节点会自动被移除。
  • 临时顺序节点:在临时节点特性的基础上增加了节点先后顺序的能力。
    Dubbo只使用了持久节点和临时节点两种,对顺序没有需求。
    /dubbo/com.foo.BarService/providers是Provider在zookeeper注册中心的路径示例,是一种树形结构,该结构分4层:root(根节点,对应示例中的dubbo)、service(接口名称,对应com.foo.BarService)、四种服务目录(对应示例中的providers,其他还包括consumers、routers、configurators)。再往后就是具体的Dubob服务URL。
    树形结构的关系:
    (1) 树的根节点是注册中心的分组,下面有多个服务接口,这个分组值来自用户配置<dubbo:registry>中的group树形,默认值是/dubbo。
    (2) 根目录下面是service目录,service目录下包含4类子目录,分别是providers、consumers、routers、configurators,都是持久节点。
    (3) providers目录下面包含有该service的多个服务者URL元数据信息。
    (4) consumers目录下面包含有该service的多个消费者URL元数据信息。
    (5) routers目录下面包含多个用于消费者路由策略的URL元数据信息。
    (6) configurators下面包含多个用于服务者动态配置的URL元数据信息。(只用于服务者吗?貌似只是大部分吧)


    zookeeper服务信息结构

    在Dubbo框架启动时,会根据用户配置的服务,在注册中心创建4各目录,在providers和consumers目录分别存储服务提供方、消费方元数据信息,主要包括IP、端口、权重和应用名等数据。
    在Dubbo框架进行服务调用时,用户可以通过服务治理平台(dubbo-admin)下发路由配置。如果要在运行时改变服务参数,则用户也可以通过服务治理平台下发动态配置。服务器端会通过订阅机制收到属性变更,并重新更新已经暴露的服务。

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
  • 注意点1 zkClient.addChildListener()会返回子节点数据列表,即首次订阅的全量拉取的表现。
  • 注意点2 从代码可以看出只对第一层根节点和第3层节点进行了订阅,第二层service节点没有进行订阅,估计是因为第2层节点的子节点最多只有4种不会动态变更的原因。
  • 注意点3 最后一步notify的操作,其实是获得了第4层的全量url,所以需要通知更新。如果是providers类别的数据,则订阅方会更新本地Directory管理的Invoker服务列表;如果是routers分类,则更新本地路由规则列表;如果是configurators,则更新动态参数列表。

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添加到对应的重试集合中,以供定时器去重试。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容