1. 基本原理
1.1 角色
- 服务提供者 (RPC server / Provider)
- 服务消费者 (RPC Client / Consumer)
- 注册中心(Register)
其中服务注册和发现的过程都依赖于注册中心:
- 服务提供者将接口信息以注册到注册中心
- 服务消费者从注册中心读取和订阅服务提供者的地址信息
- 如果有可用的服务,注册中心会主动通知服务消费者
- 服务消费者根据可用服务的地址列表,调用服务提供者的接口
1.2 应用内注册与发现
注册中心提供服务端和客户端的SDK,业务应引入SDK,通过SDK与注册中心交互,来实现服务的注册和发现
如Netflix开源的Eureka:
- Eureka Server:注册中心的服务端,实现了服务信息注册、存储以及查询等功能。
- Eureka Client:
- 服务提供者通过调用 SDK,实现服务注册、反注册等功能。
- 服务消费者通过调用 SDK,实现服务订阅、服务更新等功能。
1.3 应用外注册与发现
业务应用本身不需要通过 SDK 与注册中心打交道,而是通过其他方式与注册中心交互,间接完成服务注册与发现。
2. 注册中心的基本功能
2.1 元数据定义
常见的定义服务元数据的方式有:
- XML 文件 - 如果只是企业内部之间的服务调用,并且都是 Java 语言的话,选择 XML 配置方式是最简单的。
- IDL 文件 - 如果企业内部存在多个跨语言服务,建议使用 IDL 文件方式进行描述服务。
- REST API - 如果存在对外开放服务调用的情形的话,使用 REST API 方式则更加通用
2.2 元数据存储
注册中心本质上是一个用于保存元数据的分布式存储,存储的信息如下:
- 服务节点信息,如 IP、端口等。
- 接口定义,如接口名、请求参数、响应参数等
- 请求失败的重试次数
- 序列化方式
- 压缩方式
- 通信协议
在具体存储时,注册中心一般会按照“服务 - 分组 - 节点信息”的层次化的结构来存储。
分组的概念可以是:
- namespace维度:例如生产、测试、预发
- 区域的维度:华北机房、华南机房等
2.3 注册中心 API
既然是分布式存储,势必要提供支持读写数据的接口,一般来说,需要支持以下功能:
- 服务注册接口
- 服务反注册接口(注销)
- 心跳汇报接口:用来检测节点存活状态
- 服务订阅接口
- 服务变更查询接口:获取最新的服务节点信息
- 服务查询接口:查询服务信息(管理能力)
- 服务修改接口:修改注册中心中某一服务的信息(管理能力)
2.4 服务健康检测
注册中心通常使用长连接或心跳探测方式检查服务健康状态。
ZooKeeper:基于服务端和客户端长连接和会话超时控制机制,来实现服务健康状态检测的
- 客户端和服务端建立连接后,会话也随之建立,并生成一个全局唯一的 Session ID
- 在SESSION_TIMEOUT周期内,服务端会检测与客户端的链路是否正常,发送心跳ping,并重置下次SESSION_TIMEOUT 时间
- 如果超过 SESSION_TIMEOUT 后服务端都没有收到客户端的心跳消息,则服务端认为这个 Session 就已经结束了
2.5 服务状态变更通知
一旦注册中心探测到有服务提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。注册中心通常基于服务状态订阅来实现服务状态变更通知。
ZooKeeper:基于watch机制
2.6 集群部署
注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。
- CP型:牺牲可用性来换取数据强一致性
- ZooKeeper 集群内只有一个 Leader,而且在 Leader 无法使用的时候通过 Paxos 算法选举出一个新的 Leader。这个 Leader 的目的就是保证写信息的时候只向这个 Leader 写入,Leader 会同步信息到 Followers,这个过程就可以保证数据的强一致性。但如果多个 ZooKeeper 之间网络出现问题,造成出现多个 Leader,发生脑裂的话,注册中心就不可用了。
- etcd 和 Consul 集群内都是通过 Raft 协议来保证强一致性,如果出现脑裂的话, 注册中心也不可用
- AP型:牺牲一致性(只保证最终一致性)来换取可用性
- Eureka 不用选举一个 Leader,每个 Eureka 服务器单独保存服务注册地址,因此有可能出现数据信息不一致的情况。但是当网络出现问题的时候,每台服务器都可以完成独立的服务。
3. 其他功能
3.1 并行订阅服务
解决:当服务订阅的数量较多时,如果某一个服务链接超时,导致整个服务启动缓慢的问题。
通过多线程的方式去订阅
3.2 批量注销服务
由于网络原因,导致偶发的反注册调用失败会导致不可用的节点残留在注册中心中,变成“僵尸节点”。注册中心需要定时去清理注册中心中的“僵尸节点”,批量注销服务,就可以一次调用就把该节点上提供的所有服务同时注销掉。
3.3 服务变更信息增量更新
通过增量更新的方式(注册中心只返回变化的那部分节点信息),从而最大程度避免产生网络风暴。
3.4 心跳开关保护机制
解决:当网络频繁抖动的情况下,注册中心中可用的节点会不断变化,导致服务消费者不断地请求注册中心来拉取最新的可用服务节点信息,可能会把注册中心的带宽给占满。
建立保护机制:注册中心设置一个开关,可以作为一个紧急措施,合理利用
- 当开关打开时,即使网络频繁抖动,注册中心也不会通知所有的服务消费者有服务节点信息变更,只通知一定比例的服务消费者。
- 代价就是服务消费者感知最新的服务节点信息会延迟
3.5 服务节点摘除保护机制
如果遇到网络问题,注册中心会移除的节点数量过少,造成剩下的可用节点难以承受所有的调用,引起“雪崩” (但其实这些被摘除的节点是可用的)
设置一个阈值比例:注册中心不能摘除超过这个阈值比例的节点
3.6 白名单机制
由于集群有各种环境,开发和测试过程中可能会错误的把测试环境下的服务节点注册到了线上注册中心集群,导致线上流量就会调用到测试环境下server
注册中心可以提供一个白名单机制,只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口。
3.7 静态注册中心
- 服务消费者端,根据调用服务提供者是否成功来判定服务提供者是否可用。如果服务消费者调用某一个服务提供者节点连续失败超过一定次数,可以在本地内存中将这个节点标记为不可用
- 每隔一段固定时间,服务消费者都要向标记为不可用的节点发起保活探测,如果探测成功了,就将标记为不可用的节点再恢复为可用状态,重新发起调用