【Dubbo】注册中心及原理

主要内容 :

  • 注册中心的工作流程 ;
  • 注册中心的数据结构 ;
  • 订阅发布的实现 ;
  • 缓存机制 ;
  • 重试机制 ;
  • 设计模式 。

首先介绍整个注册中心的总体工作流程 ; 其次讲解不同类型注册中心的数据结构和实现原理 ; 接着讲解注册中心支持的通用特性 , 如缓存机制 、 重试机制 ; 最后会对整个注册中心的设计模式做深入解析 。

1 注册中心概述

在 Dubbo 微服务体系中 , 注册中心是其核心组件之一 。 Dubbo 通过注册中心实现了分布式环境中各服务之间的注册与发现 , 是各个分布式节点之间的纽带 。 其主要作用如下 :

  • 动态加入 。 一个服务提供者通过注册中心可以动态地把自己暴露给其他消费者 , 无须消费者逐个去更新配置文件
  • 动态发现 。 一个消费者可以动态地感知新的配置 、 路由规则和新的服务提供者 , 无须重启服务使之生效 。
  • 动态调整 。 注册中心支持参数的动态调整 , 新参数自动更新到所有相关服务节点 。
  • 统一配置 。 避免了本地配置导致每个服务的配置不一致问题 。

Dubbo 的注册中心源码在模块 dubbo-registry 中 , 里面包含了五个子模块 , 如表 3-1 所示
在这里插入图片描述

从 dubbo-registry 的模块中可以看到 , Dubbo 主要包含四种注册中心的实现 , 分别是ZooKeeper 、 Redis 、 Simple 、 Multicast

其中 ZooKeeper 是官方推荐的注册中心 , 在生产环境中有过实际使用 , 具体的实现在 Dubbo 源码的 dubbo-registry-zookeeper 模块中 。 阿里内部并没有使用 Redis 作为注册中心 , Redis 注册中心并没有经过长时间运行的可靠性验证 , 其稳定性依赖于 Redis 本身 。 Simple 注册中心 是一个简单的基于内存的注册中心实现 , 它本身就是一个标准的 RPC 服务 , 不支持集群 , 也可能出现单点故障 。 Multicast 模式则不需要启动任何注册中心 , 只要通过广播地址 , 就可以互相发现 。 服务提供者启动时 , 会广播自己的地址 。 消费者启动时 , 会广播订阅请求 , 服务提供者收到订阅请求 , 会根据配置广播或单播给订阅者 。 不建议在生产环境使用 。

Dubbo 拥有良好的扩展性 , 如果以上注册中心都不能满足需求 , 那么用户可以基于 RegistryFactory 和 Registry 自行扩展 。 后面章节会专门介绍注册中心的扩展 。

1.1 工作流程

注册中心的总体流程比较简单 , Dubbo 官方也有比较详细的说明 , 总体流程如图所示 。
在这里插入图片描述

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

1.2 数据结构

注册中心的总体流程相同 , 但是不同的注册中心有不同的实现方式 , 其数据结构也不相同 。ZooKeeper. Redis 等注册中心都实现了这个流程 。 由于有些注册中心并不常用 , 因此只分析 ZooKeeper 和 Redis 两种实现的数据结构 。

1.3 ZooKeeper 原理概述

ZooKeeper 是树形结构的注册中心 , 每个节点的类型分为持久节点 、 持久顺序节点 、 临时节点和临时顺序节点 。

  • 持久节点 : 服务注册后保证节点不会丢失 , 注册中心重启也会存在 。
  • 持久顺序节点 : 在持久节点特性的基础上增加了节点先后顺序的能力 。
  • 临时节点 : 服务注册后连接丢失或 session 超时 , 注册的节点会自动被移除 。
  • 临时顺序节点 : 在临时节点特性的基础上增加了节点先后顺序的能力 。

Dubbo 使用 ZooKeeper 作为注册中心时 , 只会创建持久节点和临时节点两种 , 对创建的顺序并没有要求 。

/dubbo/com/foo/BarService/providers 是服务提供者在 ZooKeeper 注册中心的路径示例 ,是一种树形结构 , 该结构分为四层 : root ( 根节点 , 对应示例中的 dubbo ) 、 service ( 接口名称 对应示例中的 com.foo.BarService )> 四种服务目录(对应示例中的 providers, 其他目录还有 consumers 、 routers 、 configurators) 。 在服务分类节点下是具体的 Dubbo 服务 URL 。

树形结构示例如下 :
在这里插入图片描述

树形结构的关系:

  • (1) 树的根节点是注册中心分组 , 下面有多个服务接口 , 分组值来自用户配置 <dubbo:registry> 中的 group 属性 , 默认是 /dubbo 。
  • (2) 服务接口下包含 4 类子目录 , 分别是 providers 这个路径是持久节点 。consumers > routers > configurators,
  • (3) 服务提供者目录 (/dubbo/service/providers) 元数据信息 。
  • (4) 服务消费者目录 (/dubbo/service/consumers)元数据信息 。
  • (5) 路由配置目录 (/dubbo/service/routers) 下面包含多个用于消费者路由策略元数据信息 。 路由配置会在第 7 章详细介绍 。
  • (6) 动态配置目录 (/dubbo/service/configurators) 下面包含多个用于服务者动态配置URL 元数据信息 。 动态配置也会在第 7 章详细介绍 。

下面通过树形结构做一个简单的演示 , 如图 所示
在这里插入图片描述

在 Dubbo 框架启动时 , 会根据用户配置的服务 , 在注册中心中创建 4 个目录 , 在 providers和 consumers 目录中分别存储服务提供方 、 消费方元数据信息 , 主要包括 IP 、 端口 、 权重和应用名等数据 。

在 Dubbo 框架进行服务调用时 , 用户可以通过服务治理平台 ( dubbo-admin) 下发路由配置 。 如果要在运行时改变服务参数 , 则用户可以通过服务治理平台 ( dubbo-admin) 下发动态配置 。 服务器端会通过订阅机制收到属性变更 , 并重新更新已经暴露的服务 。

在深入讲解 Dubbo 内部原理之前 , 可以先参考表中目录包含的信息
在这里插入图片描述
服务元数据中的所有参数都是以键值对形式存储的 。 以服务元数据为例 :dubbo://192 .168 .0.1.20880/com . alibaba.demo.Service?category=provider&name=demo-provider&. . ,服务元数据中包含 2 个键值对 , 第 1 个 key 为 category, key 关联的值为 provider 。在 Dubbo 中启用注册中心可以参考如下方式

<beans>
<! -- 适用于 ZooKeeper — 个集群有多个节点 , 多个 IP 和端口用逗号分隔 -->
<dubbo:registry protocol="zookeeper" address="ip:port > ip:port" />

<!-- 适用于 ZooKeeper 多个集群有多个节点 , 多个 IP 和端口用竖线分隔 -->
<dubbo:registry protocol="zookeeper" address="ip:port | ip:port" />
</beans>

1.4 Redis 原理概述

Redis 注册中心也沿用了 Dubbo 抽象的 Root 、 Service 、 Type、 URL 四层结构 。 但是由于 Redis属于 NoSQL 数据库 , 数据都是以键值对的形式保存的 , 并不能像 ZooKeeper- 样直接实现树形目录结构 。 因此 , Redis 使用了 key/Map 结构实现了这个需求 , Root 、 Service 、 Type 组合成 Redis 的 key 。 Redis 的 value 是一个 Map 结构 , URL 作为 Map 的 key, 超时时间作为 Map 的 value
在这里插入图片描述

数据结构的组装逻辑在 org.apache.dubbo.registry.redis.RedisRegistry#doRegister (URL url) 方法中 ,

doRegister 源码
在这里插入图片描述

2 订阅/发布

订阅 / 发布是整个注册中心的核心功能之一 。 在传统应用系统中 , 我们通常会把配置信息写入一个配置文件 , 当配置需要变更时会修改配置文件,再通过手动触发内存中的配置重新加载 ,如重启服务等 。 在集群规模较小的场景下 , 这种方式也能方便地进行运维 。 当服务节点数量不断上升的时候 , 这种管理方式的弊端就会凸显出来 。

如果我们使用了注册中心 , 那么上述的问题就会迎刃而解 。 当一个已有服务提供者节点下线 , 或者一个新的服务提供者节点加入微服务环境时 , 订阅对应接口的消费者和服务治理中心都能及时收到注册中心的通知 , 并更新本地的配置信息 。 如此一来 , 后续的服务调用就能避免调用已经下线的节点 , 或者能调用到新的节点 。 整个过程都是自动完成的 , 不需要人工参与 。Dubbo 在上层抽象了这样一个工作流程 , 但可以有不同的实现 。

2.1 ZooKeeper 的实现

RegistryProtocol 对于 export 的实现
在这里插入图片描述
RegistryProtocol 对于 refer 实现

  • 其向 ZooKeeperRegistry 订阅是在 Directory#subscribe
    在这里插入图片描述

1. ZooKeeperRegistry 发布的实现

服务提供者和消费者都需要把自己注册到注册中心 。 服务提供者的注册是为了让消费者感知服务的存在 , 从而发起远程调用 ; 也让服务治理中心感知有新的服务提供者上线 。 消费者的发布是为了让服务治理中心可以发现自己 。 ZooKeeper 发布代码非常简单 , 只是调用了ZooKeeper 的客户端库在注册中心上创建一个目录

Dubbo 用 zkClient 创建目录源码
在这里插入图片描述

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

Dubbo 用 zkClient 删除路径源码
在这里插入图片描述

2. ZooKeeperRegistry 订阅的实现

订阅通常有 pull 和 push 两种方式 , 一种是客户端定时轮询注册中心拉取配置 , 另一种是注册中心主动推送数据给客户端 。 这两种方式各有利弊 , 目前 Dubbo 采用的是第一次启动拉取方式 , 后续接收事件重新拉取数据 。

在服务暴露时 , 服务端会订阅 configurators 用于监听动态配置 , 在消费端启动时 , 消费端会订阅 providers 、 routers 和 configuratops 这三个目录 , 分别对应服务提供者 、 路由和动态配置变更通知 。

Dubbo 中有哪些 ZooKeeper 客户端实现 ?
无论服务提供者还是消费者 , 或者是服务治理中心 , 任何一个节点连接到 ZooKeeper 注册中心都需要使用一个客户端 , Dubbo 在 dubbo-remoting-zookeeper 模块中实现了 ZooKeeper 客户端的统一封装 , 定义了统一的 Client API, 并用两种不同的 ZooKeeper 开源客户端库实现了这个接口 :
• Apache Curator ;
• zkClient
用户可以在 <dubbo: registry> 的 client 属性中设置 curator 、 zkclient 来使用不同的客户端实现库 , 如果不设置则默认使用 Curator 作为实现 。

ZooKeeper 注册中心采用的是 “ 事件通知 ” + “ 客户端拉取 ” 的方式 , 客户端在第一次连接上注册中心时 , 会获取对应目录下全量的数据 。 并在订阅的节点上注册一个 watcher, 客户端与注册中心之间保持 TCP 长连接 , 后续每个节点有任何数据变化的时候 , 注册中心会根据 watcher 的回调主动通知客户端 ( 事件通知 ) , 客户端接到通知后 , 会把对应节点下的全量数据都拉取过来 ( 客户端拉取 ) , 这一点在 NotifyListener#notify ( List<URL> urls ) 接口上就有约束的注释说明 。 全量拉取有一个局限 , 当微服务节点较多时会对网络造成很大的压力 。

ZooKeeper 的每个节点都有一个版本号 , 当某个节点的数据发生变化 ( 即事务操作 ) 时 ,该节点对应的版本号就会发生变化 , 并触发 watcher 事件 , 推送数据给订阅方 。 版本号强调的是变更次数 , 即使该节点的值没有变化 , 只要有更新操作 , 依然会使版本号变化 。

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

分类别订阅

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

ZooKeeperRegistry#doSubscribe 订阅类别服务
在这里插入图片描述

注意 , 此处会根据 URL 中的 category 属性值获取具体的类别 : providers 、 routers 、consumers、 configurators, 然后拉取直接子节点的数据进行通知 ( notify) 。 如果是 providers类别的数据 , 则订阅方会更新本地 Directory 管理的 Invoker 服务列表 ; 如果是 routers 分类 , 则订阅方会更新本地路由规则列表 ; 如果是 configuators 类别 , 则订阅方会更新或覆盖本地动态参数列表

说明

  • ZooKeeper 注册的监听器实际上是 ChildListener,其会监听当前目录的子节点,当有变化时在 childListener 中调用的 ZooKeeperRegistry#notify 会回调用 NotifyListener(即上面看到的 RegistryDirectory 和 OverrideListener)
    在这里插入图片描述

全量订阅

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

ZooKeeperRegistry#doSubscribe 全量订阅服务
在这里插入图片描述
可以得知 , 此处主要支持 Dubbo 服务治理平台 ( dubbo-admin), 平台在启动时会订阅全量接口 , 它会感知每个服务的状态 。

说明

  • 全量订阅,在 ChildLisener 递归订阅的时候,会给订阅的 url 增加订阅类型限制,即 providers 、 routers 、consumers、 configurators,然后就会成为上面说的分类别订阅

2.2 Redis 的实现

1 .总体流程

使用 Redis 作为注册中心 , 其订阅发布实现方式与 ZooKeeper 不同 。 我们在 Redis 注册中心的数据结构中已经了解到 , Redis 订阅发布使用的是过期机制和 publish/subscribe 通道 。 服务提供者发布服务 , 首先会在 Redis 中创建一个 key, 然后在通道中发布一条 register 事件消息 。

但服务的 key 写入 Redis 后 , 发布者需要周期性地刷新 key 过期时间 , 在 RedisRegistry 构造方法中会启动一个 expireExecutor 定时调度线程池 , 不断调用 deferExpired( ) 方法去延续 key的超时时间 。 如果服务提供者服务宕机 , 没有续期 , 则 key 会因为超时而被 Redis 删除 , 服务也就会被认定为下线

Redis 续期 key
在这里插入图片描述

订阅方首次连接上注册中心 , 会获取全量数据并缓存在本地内存中 。 后续的服务列表变化则通过 publish/subscribe 通道广播 , 当有服务提供者主动下线的时候 , 会在通道中广播一条 unregister 事件消息 , 订阅方收到后则从注册中心拉取数据 , 更新本地缓存的服务列表 。 新服务提供者上线也是通过通道事件触发更新的 。

但是 , Redis 的 key 超时是不会有动态消息推送的 , 如果服务提供者宕机而不是主动下线 ,则造成没有广播 unregister 事件消息 , 订阅方是如何知道服务的发布方已经下线了呢 ? 另外 ,Redis 的 publish/subscribe 通道并不是消息可靠的 , 如果 Dubbo 注册中心使用了 failover 的集群容错模式 , 并且消费者订阅了从节点 , 但是主节点并没有完成数据同步给从节点就宕机 , 后续
订阅方要如何知道服务发布方己经下线呢 ?

如果使用 Redis 作为服务注册中心 , 会依赖于服务治理中心 。 如果服务治理中心定时调度 ,则还会触发清理逻辑 : 获取 Redis 上所有的 key 并进行遍历 , 如果发现 key 已经超时 , 则删除 Redis上对应的 key 。 清除完后 , 还会在通道中发起对应key 的 unregister 事件 , 其他消费者监听到取消注册事件后会删除本地对应服务的数据 , 从而保证数据的最终一致

过期 key 清理
在这里插入图片描述

由上面的机制可以得出整个 Redis 注册中心的工作流程 , 如图 3-4 所示
在这里插入图片描述

Redis 客户端初始化的时候 , 需要先初始化 Redis 的连接池 jedisPools, 此时如果配置注册中心的集群模式为 <dubbo:registry cluster= ”replicate"/> , 则服务提供者在发布服务的时候,需要同时向 Redis 集群中所有的节点都写入 , 是多写的方式 。 但读取还是从一个节点中读取 。在这种模式下 , Redis 集群可以不配置数据同步 , 一致性由客户端的多写来保证 。如果设置为 failover 或不设置 , 则只会读取和写入任意一个 Redis 节点 , 失败的话再尝试下一个 Redis 节点 。 这种模式需要 Redis 自行配置数据同步

另外 , 在初始化阶段 , 还会初始化一个定时调度线程池 expireExecutor, 它主要的任务是延长 key 的过期时间和删除过期的 key 。线程池调度的时间间隔是超时时间的一半

2. 发布的实现

服务提供者和消费者都会使用注册功能 , Redis 注册部分的关键源码

Redis 注册代码
在这里插入图片描述
3 .订阅的实现

服务消费者 、 服务提供者和服务治理中心都会使用注册中心的订阅功能 。 在订阅时 , 如果是首次订阅 , 则会先创建一个 Notifier 内部类 , 这是一个线程类 , 在启动时会异步进行通道的订阅 。 在启动 Notifier 线程的同时 , 主线程会继续往下执行 , 全量拉一次注册中心上所有的服务信息 。 后续注册中心上的信息变更则通过 Notifier 线程订阅的通道推送事件来实现 。 下面是Notifier 线程中通道订阅的逻辑

Redis 订阅代码
在这里插入图片描述

3 缓存机制

缓存的存在就是用空间换取时间 , 如果每次远程调用都要先从注册中心获取一次可调用的服务列表 , 则会让注册中心承受巨大的流量压力 。 另外 , 每次额外的网络请求也会让整个系统的性能下降 。 因此 Dubbo 的注册中心实现了通用的缓存机制 , 在抽象类 AbstractRegistry 中实现 。
在这里插入图片描述
缓存的存在就是用空间换取时间 , 如果每次远程调用都要先从注册中心获取一次可调用的服务列表 , 则会让注册中心承受巨大的流量压力 。 另外 , 每次额外的网络请求也会让整个系统的性能下降 。 因此 Dubbo 的注册中心实现了通用的缓存机制 , 在抽象类 AbstractRegistry 中实现 。

消费者或服务治理中心获取注册信息后会做本地缓存 。 内存中会有一份 , 保存在 Properties对象里 , 磁盘上也会持久化一份件 , 通过 file 对象引用 。 在 AbstractRegistry 抽象类中有如下定义

AbstractRegistry 定义
在这里插入图片描述

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

3.1 缓存的加载

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

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

3.2 缓存的保存与更新

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

同步与异步更新缓存
在这里插入图片描述

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

4 重试机制

我们可以得知 com.alibaba.dubbo.registry . support . FailbackRegistry 继承了 AbstractRegistry, 并在此基础上增加了失败重试机制作为抽象能力 。 ZookeeperRegistry 和RedisRegistry 继承该抽象方法后 , 直接使用即可 。FailbackRegistry 抽象类中定义了一个 ScheduledExecutorService , 每经过固定间隔(默认为 5 秒)调用 FailbackRegistry#retry () 方法 。 另外 , 该抽象类中还有五个比较重要的集合 ,如表所示
在这里插入图片描述
在定时器中调用 retry 方法的时候 , 会把这五个集合分别遍历和重试 , 重试成功则从集合中移除 。 FailbackRegistry 实现了 subscribe > unsubscribe 等通用方法 , 里面调用了未实现的模板方法 , 会由子类实现 。 通用方法会调用这些模板方法 , 如果捕获到异常 , 则会把 URL 添加到对应的重试集合中 , 以供定时器去重试

5 设计模式

Dubbo 注册中心拥有良好的扩展性 , 用户可以在其基础上 , 快速开发出符合自己业务需求的注册中心 。 这种扩展性和 Dubbo 中使用的设计模式密不可分

5.1 模板模式

整个注册中心的逻辑部分使用了模板模式 , 其类的关系如图所示
在这里插入图片描述
AbstractRegistry 实现了 Registry 接口中的注册 、 订阅 、 查询 、 通知等方法 , 还实现了磁盘文件持久化注册信息这一通用方法 。 但是注册 、 订阅 、 查询 、 通知等方法只是简单地把 URL加入对应的集合 , 没有具体的注册或订阅逻辑 。

FailbackRegistry 又继承了 AbstractRegistry, 重写了父类的注册 、 订阅 、 查询和通知等方法 , 并且添加了重试机制 。 此外 , 还添加了四个未实现的抽象模板方法 , 如代码清单

未实现的抽象模板方法
在这里插入图片描述

以订阅为例 , FailbackRegistry 重写了 subscribe 方法 , 但只实现了订阅的大体逻辑及异常处理等通用性的东西 。 具体如何订阅 , 交给继承的子类实现 。 这就是模板模式的具体实现

模板模式调用
在这里插入图片描述

5.2 工厂模式

所有的注册中心实现 , 都是通过对应的工厂创建的 。 工厂类之间的关系如图所示
在这里插入图片描述

AbstractRegistryFactory 实现了 RegistryFactory 接口的 getRegistry(URL url) 方法 , 是一个通用实现 , 主要完成了加锁, 以及调用抽象模板方法 createRegistry(URL url) 创建具体实现等操作 , 并缓存在内存中 。 抽象模板方法会由具体子类继承并实现

getRegistry 抽象实现
在这里插入图片描述
虽然每种注册中心都有自己具体的工厂类 , 但是在什么地方判断 , 应该调用哪个工厂类实现呢 ? 代码中并没有看到显式的判断 。 答案就在 RegistryFactory 接口中 , 该接口里有一个Registry getRegistry(URL url) 方法 , 该方法上由 @Adaptive({“protocol”)) 注解

RegistryFactory 源码
在这里插入图片描述
了解 AOP 的读者就会很容易理解 , 这个注解会自动生成代码实现一些逻辑 , 它的 value 参数会从 URL 中获取 protocol 键的值 , 并根据获取的值来调用不同的工厂类 。 例如 , 当url.protocol = redis 时 , 获得 RedisRegistryFactory 实现类 。

6 小结

介绍了 Dubbo 中已经支持的注册中心 。 重点介绍了 ZooKeeper 和 Redis 两种注册中心 。讲解了两种注册中心的数据结构 , 以及订阅发布机制的具体实现 。然后介绍了注册中心中一些通用的关键特性 , 如数据缓存 、 重试等机制 。 最后 , 在对各种机制己经了解的前提下 , 讲解了整个注册中心源码的设计模式 。

相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页