Dubbo 底层原理系列(五):注册中心
2020-08-16·5 分钟阅读
前言
注册中心是 Dubbo 实现服务发现的核心组件,负责服务的注册、订阅和变更通知。理解注册中心原理对于排查服务发现问题至关重要。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| Registry 接口 | ⭐⭐⭐ | 高频考点 | ✅ |
| Zookeeper 实现 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 订阅通知机制 | ⭐⭐⭐⭐ | 进阶考点 | ✅ |
| 缓存机制 | ⭐⭐⭐ | 进阶考点 | ✅ |
面试考点
- Dubbo 支持哪些注册中心?
- Zookeeper 注册中心是如何实现的?
- 服务订阅和变更通知是如何工作的?
- 注册中心缓存的作用是什么?
注册中心接口
Registry 接口设计
┌─────────────────────────────────────────────────────────────────────────┐
│ Registry 接口设计 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ RegistryService 接口: │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ public interface RegistryService { │ │ │
│ │ │ │ │ │
│ │ │ // 注册服务 │ │ │
│ │ │ void register(URL url); │ │ │
│ │ │ │ │ │
│ │ │ // 取消注册 │ │ │
│ │ │ void unregister(URL url); │ │ │
│ │ │ │ │ │
│ │ │ // 订阅服务 │ │ │
│ │ │ void subscribe(URL url, NotifyListener listener); │ │ │
│ │ │ │ │ │
│ │ │ // 取消订阅 │ │ │
│ │ │ void unsubscribe(URL url, NotifyListener listener); │ │ │
│ │ │ │ │ │
│ │ │ // 查询服务列表 │ │ │
│ │ │ List<URL> lookup(URL url); │ │ │
│ │ │ } │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Registry 接口(继承 Node): │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ public interface Registry extends RegistryService, Node {│ │ │
│ │ │ URL getUrl(); // 获取注册中心 URL │ │ │
│ │ │ boolean isAvailable(); // 是否可用 │ │ │
│ │ │ void destroy(); // 销毁 │ │ │
│ │ │ } │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
RegistryFactory 接口
┌─────────────────────────────────────────────────────────────────────────┐
│ RegistryFactory 接口 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ @SPI("dubbo") │ │
│ │ public interface RegistryFactory { │ │
│ │ │ │
│ │ @Adaptive({"protocol"}) │ │
│ │ Registry getRegistry(URL url); │ │
│ │ } │ │
│ │ │ │
│ │ 实现类: │ │
│ │ • ZookeeperRegistryFactory │ │
│ │ • NacosRegistryFactory │ │
│ │ • RedisRegistryFactory │ │
│ │ • MulticastRegistryFactory │ │
│ │ • SimpleRegistryFactory (Simple/Direct) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 支持的注册中心: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 注册中心 配置示例 特点 │ │
│ │ ───────────────────────────────────────────────────────────── │ │
│ │ Zookeeper zookeeper://127.0.0.1:2181 推荐,生产可用 │ │
│ │ Nacos nacos://127.0.0.1:8848 阿里云,功能丰富 │ │
│ │ Redis redis://127.0.0.1:6379 简单,性能好 │ │
│ │ Multicast multicast://224.5.6.7:1234 无需中心节点 │ │
│ │ Simple dubbo://127.0.0.1:9090 点对点直连 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Zookeeper 注册中心
架构设计
┌─────────────────────────────────────────────────────────────────────────┐
│ Zookeeper 注册中心架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ZookeeperRegistry │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 继承:FailbackRegistry(失败重试) │ │ │
│ │ │ │ │ │
│ │ │ 核心属性: │ │ │
│ │ │ • ZookeeperClient zkClient // ZK 客户端 │ │ │
│ │ │ • Set<String> anyServices // 所有服务 │ │ │
│ │ │ • ConcurrentMap<URL, NotifyListener> listeners │ │ │
│ │ │ │ │ │
│ │ │ 核心方法: │ │ │
│ │ │ • doRegister(URL url) // 注册实现 │ │ │
│ │ │ • doUnregister(URL url) // 取消注册实现 │ │ │
│ │ │ • doSubscribe(URL url, NotifyListener listener) │ │ │
│ │ │ • doUnsubscribe(URL url, NotifyListener listener) │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
节点结构
┌─────────────────────────────────────────────────────────────────────────┐
│ Zookeeper 节点结构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 默认根节点:/dubbo │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ /dubbo │ │
│ │ │ │ │
│ │ └── com.example.UserService (服务接口) │ │
│ │ │ │ │
│ │ ├── providers (服务提供者) │ │
│ │ │ │ │ │
│ │ │ ├── dubbo://192.168.1.1:20880/... │ │
│ │ │ └── dubbo://192.168.1.2:20880/... │ │
│ │ │ │ │
│ │ ├── consumers (服务消费者) │ │
│ │ │ │ │ │
│ │ │ └── consumer://192.168.1.100/... │ │
│ │ │ │ │
│ │ ├── routers (路由规则) │ │
│ │ │ │ │ │
│ │ │ └── condition://... │ │
│ │ │ │ │
│ │ └── configurators (动态配置) │ │
│ │ │ │ │
│ │ └── override://... │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 节点类型: │
│ • providers:临时节点,服务下线自动删除 │
│ • consumers:临时节点,消费者下线自动删除 │
│ • routers:持久节点,存储路由规则 │
│ • configurators:持久节点,存储动态配置 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
注册实现
┌─────────────────────────────────────────────────────────────────────────┐
│ doRegister 实现 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ protected void doRegister(URL url) { │ │
│ │ try { │ │
│ │ // 构建节点路径 │ │
│ │ String path = toUrlPath(url); │ │
│ │ // 例如:/dubbo/com.example.UserService/providers/ │ │
│ │ // dubbo://192.168.1.1:20880/... │ │
│ │ │ │
│ │ // 创建临时节点 │ │
│ │ zkClient.create(path, true); │ │
│ │ // true 表示临时节点 │ │
│ │ │ │
│ │ } catch (Exception e) { │ │
│ │ throw new RpcException("Failed to register " + url); │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 注册流程: │
│ 1. 构建节点路径 │
│ 2. 创建父节点(持久节点) │
│ 3. 创建服务节点(临时节点) │
│ 4. 会话断开后自动删除临时节点 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
订阅与通知机制
订阅实现
┌─────────────────────────────────────────────────────────────────────────┐
│ doSubscribe 实现 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ protected void doSubscribe(URL url, NotifyListener listener) { │ │
│ │ │ │
│ │ // 1. 构建监听路径 │ │
│ │ String path = toCategoryPath(url); │ │
│ │ // 例如:/dubbo/com.example.UserService/providers │ │
│ │ │ │
│ │ // 2. 添加子节点监听器 │ │
│ │ List<String> children = zkClient.addChildListener( │ │
│ │ path, │ │
│ │ new ChildListener() { │ │
│ │ public void childChanged(String path, │ │
│ │ List<String> children) {│ │
│ │ // 子节点变化时触发通知 │ │
│ │ notify(url, listener, toUrls(children)); │ │
│ │ } │ │
│ │ } │ │
│ │ ); │ │
│ │ │ │
│ │ // 3. 首次获取并通知 │ │
│ │ notify(url, listener, toUrls(children)); │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 订阅流程: │
│ 1. 构建监听路径 │
│ 2. 注册子节点监听器 │
│ 3. 首次获取子节点列表并通知 │
│ 4. 后续变化通过监听器触发通知 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
通知机制
┌─────────────────────────────────────────────────────────────────────────┐
│ 通知机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Zookeeper 节点变化 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ChildListener.childChanged() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ AbstractRegistry.notify(url, listener, urls) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. 保存到本地缓存 │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ saveProperties(url, urls) │ │ │
│ │ │ │ │ │ │
│ │ │ └──► 写入本地文件(断网恢复用) │ │ │
│ │ │ │ │ │
│ │ │ 2. 触发监听器回调 │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ listener.notify(urls) │ │ │
│ │ │ │ │ │ │
│ │ │ └──► RegistryDirectory.notify() │ │ │
│ │ │ │ │ │ │
│ │ │ └──► 刷新 Invoker 列表 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
缓存机制
本地缓存
┌─────────────────────────────────────────────────────────────────────────┐
│ 本地缓存机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 缓存文件路径: │ │
│ │ ${user.home}/.dubbo/dubbo-registry-${host}-cache.cache │ │
│ │ │ │
│ │ 缓存作用: │ │
│ │ • 注册中心宕机时,从本地缓存恢复服务列表 │ │
│ │ • 消费者启动时,优先使用本地缓存 │ │
│ │ • 提供服务降级能力 │ │
│ │ │ │
│ │ 缓存更新: │ │
│ │ • 每次收到注册中心通知时更新 │ │
│ │ • 异步写入文件 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 配置项: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ # 是否使用本地缓存 │ │
│ │ dubbo.registry.file-cache=true │ │
│ │ │ │
│ │ # 缓存文件路径 │ │
│ │ dubbo.registry.cache-file=/path/to/cache │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
FailbackRegistry 失败重试
┌─────────────────────────────────────────────────────────────────────────┐
│ FailbackRegistry 失败重试 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 失败重试机制: │ │
│ │ │ │
│ │ • 注册失败 → 放入 failedRegistered 集合 │ │
│ │ • 订阅失败 → 放入 failedSubscribed 集合 │ │
│ │ • 定时任务重试失败操作 │ │
│ │ │ │
│ │ 重试定时器: │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ // 默认 5 秒重试一次 │ │ │
│ │ │ private static final int DEFAULT_RETRY_PERIOD = 5 * 1000;│ │ │
│ │ │ │ │ │
│ │ │ ScheduledExecutorService retryExecutor = ... │ │ │
│ │ │ │ │ │
│ │ │ retryExecutor.scheduleAtFixedRate(() -> { │ │ │
│ │ │ // 重试注册 │ │ │
│ │ │ retryRegister(); │ │ │
│ │ │ // 重试订阅 │ │ │
│ │ │ retrySubscribe(); │ │ │
│ │ │ }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS); │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
总结
本文介绍了 Dubbo 注册中心原理:
| 概念 | 说明 |
|---|---|
| RegistryService | 注册、订阅、查询接口 |
| ZookeeperRegistry | 基于 ZK 的注册中心实现 |
| 订阅机制 | 子节点监听 + 通知回调 |
| 本地缓存 | 断网恢复、服务降级 |
参考资料
下一章预告
下一章将深入解析 负载均衡原理,包括:
- 负载均衡策略
- Random、RoundRobin、LeastActive 实现
- 一致性 Hash