RPC系列二、服务发现

一、服务发现

为了高可用,在生产环境中服务提供方都是以集群的方式对外提供服务,集群里面的这些 IP 随时可能变化,我们也需要用一本“通信录”及时获取到对应的服务节点,这个获取的过程我们一般叫作“服务发现”。

1、服务发现机制

对于服务调用方和服务提供方来说,其契约就是接口,相当于“通信录”中的姓名,服务节点就是提供该契约的一个具体实例。服务 IP 集合作为“通信录”中的地址,从而可以通过接口获取服务 IP 的集合来完成服务的发现。这就是 PRC 框架的服务发现机制,如下图所示:

 (1)服务注册

在服务提供方启动的时候,将对外暴露的接口注册到注册中心之中,注册中心将这个服务节点的 IP 和接口保存下来。

(2)服务订阅

在服务调用方启动的时候,去注册中心查找并订阅服务提供方的 IP,然后缓存到本地,并用于后续的远程调用。

三、服务发现技术实现

1、基于DNS服务发现

服务发现的本质,就是完成了接口跟服务提供者 IP 的映射。那能不能把服务提供者 IP 统一换成一个域名?,利用已经成熟的 DNS 机制来实现?

好,先带着这个问题,简单地看下 DNS 的流程:

 如果用 DNS 来实现服务发现,所有的服务提供者节点都配置在了同一个域名下,调用方的确可以通过 DNS 拿到随机的一个服务提供者的 IP,并与之建立长连接,这看上去并没有太大问题,但在我们业界为什么很少用到这种方案呢?不知道你想过这个问题没有,如果没有,现在可以停下来想想这样两个问题:

(1)如果这个 IP 端口下线了,服务调用者能否及时摘除服务节点呢?

(2)如果在之前已经上线了一部分服务节点,这时我突然对这个服务进行扩容,那么新上线的服务节点能否及时接收到流量呢?

这两个问题的答案都是:“不能”。这是因为为了提升性能和减少 DNS 服务的压力,DNS 采取了多级缓存机制,一般配置的缓存时间较长,特别是 JVM 的默认缓存是永久有效的,所以说服务调用者不能及时感知到服务节点的变化。

2、基于VIP服务发现

这时你可能会想,我是不是可以加一个负载均衡设备呢?将域名绑定到这台负载均衡设备上,通过 DNS 拿到负载均衡的 IP。这样服务调用的时候,服务调用方就可以直接跟 VIP 建立连接,然后由 VIP 机器完成 TCP 转发,如下图所示:

 这个方案确实能解决 DNS 遇到的一些问题,但在 RPC 场景里面也并不是很合适,原因有以下几点:

(1)搭建负载均衡设备或 TCP/IP 四层代理,需求额外成本

(2)请求流量都经过负载均衡设备,多经过一次网络传输,会额外浪费些性能

(3)负载均衡添加节点和摘除节点,一般都要手动添加,当大批量扩容和下线时,会有大量的人工操作和生效延迟

(4)在服务治理的时候,需要更灵活的负载均衡策略,目前的负载均衡设备的算法还满足不了灵活的需求。

3、基于 ZooKeeper 的服务发现

搭建一个 ZooKeeper 集群作为注册中心集群,服务注册的时候只需要服务节点向 ZooKeeper 节点写入注册信息即可,利用 ZooKeeper 的 Watcher 机制完成服务订阅与服务下发功能,整体流程如下图:

 (1)服务平台管理端先在 ZooKeeper 中创建一个服务根路径,可以根据接口名命名(例如:/service/com.demo.xxService),在这个路径再创建服务提供方目录与服务调用方目录(例如:provider、consumer),分别用来存储服务提供方的节点信息和服务调用方的节点信息。

(2)当服务提供方发起注册时,会在服务提供方目录中创建一个临时节点,节点中存储该服务提供方的注册信息。

(3)当服务调用方发起订阅时,则在服务调用方目录中创建一个临时节点,节点中存储该服务调用方的信息,同时服务调用方 watch 该服务的服务提供方目录(/service/com.demo.xxService/provider)中所有的服务节点数据。

(4)当服务提供方目录下有节点数据发生变更时,ZooKeeper 就会通知给发起订阅的服务调用方。

其实早期使用的 RPC 框架服务发现就是基于 ZooKeeper 实现的,但微服务化程度越来越高之后,ZooKeeper 集群整体压力也越来越高,尤其在集中上线的时候越发明显。“集中爆发”是在一次大规模上线的时候,当时有超大批量的服务节点在同时发起注册操作,ZooKeeper 集群的 CPU 突然飙升,导致 ZooKeeper 集群不能工作了,而且当时也无法立马将 ZooKeeper 集群重新启动,一直到 ZooKeeper 集群恢复后业务才能继续上线。

引发这次问题的根本原因就是 ZooKeeper 本身的性能问题,当连接到 ZooKeeper 的节点数量特别多,对 ZooKeeper 读写特别频繁,且 ZooKeeper 存储的目录达到一定数量的时候,ZooKeeper 将不再稳定,CPU 持续升高,最终宕机。而宕机之后,由于各业务的节点还在持续发送读写请求,刚一启动,ZooKeeper 就因无法承受瞬间的读写压力,马上宕机。

4、基于消息总线的最终一致性的注册中心

ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的数据每次发生更新操作,都会通知其它 ZooKeeper 节点同时执行更新。它要求保证每个节点的数据能够实时的完全一致,这也就直接导致了 ZooKeeper 集群性能上的下降。

而 RPC 框架的服务发现,在服务节点刚上线时,服务调用方是可以容忍在一段时间之后(比如几秒钟之后)发现这个新上线的节点的。毕竟服务节点刚上线之后的几秒内,甚至更长的一段时间内没有接收到请求流量,对整个服务集群是没有什么影响的,所以我们可以牺牲掉 CP(强制一致性),而选择 AP(最终一致),来换取整个注册中心集群的性能和稳定性。

因为要求最终一致性,可以考虑采用消息总线机制。注册数据可以全量缓存在每个注册中心内存中,通过消息总线来同步数据。当有一个注册中心节点接收到服务节点注册时,会产生一个消息推送给消息总线,再通过消息总线通知给其它注册中心节点更新数据并进行服务下发,从而达到注册中心间数据最终一致性,具体流程如下图所示:

 (1)当有服务上线,注册中心节点收到注册请求,服务列表数据发生变化,会生成一个消息,推送给消息总线,每个消息都有整体递增的版本。

(2)消息总线会主动推送消息到各个注册中心,同时注册中心也会定时拉取消息。对于获取到消息的在消息回放模块里面回放,只接受大于本地版本号的消息,小于本地版本号的消息直接丢弃,从而实现最终一致性。

(3)消费者订阅可以从注册中心内存拿到指定接口的全部服务实例,并缓存到消费者的内存里面。

(4)采用推拉模式,消费者可以及时地拿到服务实例增量变化情况,并和内存中的缓存数据进行合并。

为了性能,这里采用了两级缓存,注册中心和消费者的内存缓存,通过异步推拉模式来确保最终一致性。

另外,可能会想到,服务调用方拿到的服务节点不是最新的,所以目标节点存在已经下线或不提供指定接口服务的情况,这个时候有没有问题?这个问题我们放到了 RPC 框架里面去处理,在服务调用方发送请求到目标节点后,目标节点会进行合法性验证,如果指定接口服务不存在或正在下线,则会拒绝该请求。服务调用方收到拒绝异常后,会安全重试到其它节点。

通过消息总线的方式,我们就可以完成注册中心集群间数据变更的通知,保证数据的最终一致性,并能及时地触发注册中心的服务下发操作。在 RPC 领域精耕细作后,你会发现,服务发现的特性是允许我们在设计超大规模集群服务发现系统的时候,舍弃强一致性,更多地考虑系统的健壮性。最终一致性才是分布式系统设计中更为常用的策略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值