RPC:注册中心,帮助分布式系统定位寻址

什么是服务发现

有什么用?

为什么需要服务发现?

  • 帮助分布式系统定位寻址:客户端在上线时,需要知道由哪些服务可以提供
  • “实时”感知服务提供方的状态,监控服务提供方的上下线:为了高可用,在生产环境中服务提供方都是以集群的方式对外提供服务,集群里面的这些IP可能变化。我们现需要及时获取对应的服务节点。

服务发现的本质,就是完成接口跟服务提供者IP之间的映射

怎么实现?注册中心

举个例子,nginx是一个反向代理组件,那么nginx需要知道,应用服务器的地址是什么,这样才能够将流量透传到应用服务器上,这就是服务发现的过程。

那么nginx是怎么实现的呢?它把应用服务器的地址配置在了文件中。但是这样做有一些缺点:

  • 首先在紧急扩容的时候,就需要修改客户端配置后,重启所有的客户端进程,操作时间比较长
  • 其次,一旦某一个服务器出现故障时,也需要修改所有客户端配置后重启,无法快速修复,更无法做到自动恢复。
  • 最后,RPC服务器上线后无法做到提前摘除流量,这样在重复服务端的时候,客户端发往被重启服务端的请求还没有返回,会造成慢请求设置请求失败

可以使用注册中心来解决这些问题。目前业界有很多可供选择的注册中心组件,比如说老派的 ZooKeeper,Kubernetes使用的 ETCD,阿里的微服务注册中心 Nacos,Spring Cloud 的 Eureka 等等。

这些注册中心的基本功能有两点:

  • 其一是提供了服务地址的存储
  • 其二是当存储内容发生变化时,可以将变更的内容推送给客户端

第二个功能是我们使用注册中心的主要原因。因为无论是,当我们需要紧急扩容,还是在服务器发生故障时,需要快速摘除节点,都不用重启服务器就可以实现了。

RPC中的服务发现

对于RPC框架来说:

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

那么怎么制作出这个“通讯录”呢?这个通讯录应该怎么使用呢?

  • 服务注册:在服务提供方启用的时候,将对外暴露的接口注册到注册中心之中,注册中心将这个服务节点的IP和接口保存下来
  • 服务订阅:在服务调用方启用的时候,去注册中心查找并订阅服务提供方的IP,然后缓存到本地,并用于后继的远程调用
    在这里插入图片描述

使用了注册中心组件之后,RPC的通信过程就变成了下面这个样子:
在这里插入图片描述
上图描述了一个完整的服务注册和发现的过程:

  • 客户端与注册中心建立连接,并且告诉注册中心,它对哪一组服务感兴趣
  • 服务端向注册中心注册服务后,注册中心会将最新的服务注册信息通知给客户端
  • 客户端拿到服务器的地址之后就可以向服务端发起调用系统了

从这个过程中可以看出,有了注册中心后,服务节点的增加和减少对于客户端来说就是透明的。

这样,除了可以实现不重启客户端,就能动态的变更服务器节点之外,还可以实现优雅关闭的功能

RPC框架中如何实现服务发现

基于ZooKeeper的服务发现

那么在RPC里面我们该如何实现呢?

注册中心要做的如下两点:

  • 完成接口和服务提供方的映射
  • 能完成实时变更推送

我们可以使用类似ZoooKeeper来完成服务发现。

整体的思路很简单,就是搭建一个ZooKeeper集群作为注册中心集群,服务注册的时候只需要向服务节点向ZooKeeper节点写入注册中心即可,利用ZooKeeper的Watcher机制完成服务订阅和服务下发功能:

  • 服务平台管理端先在ZooKeeper中创建一个服务根目录,可以通过接口名命名(比如/service/com.demo.xxService),在这个路径再创建服务提供方目录和服务调用方目录(比如provider、consumer),分别用来存储服务提供方的节点信息和服务调用方的节点信息
  • 当服务提供方发起注册时,会在服务提供方目录中创建一个临时节点,节点中存储该服务提供方的注册信息
  • 当服务调用方发起订阅时,会在服务调用目录中1创建一个临时节点,节点中存储该服务调用方的信息,同时服务调用方watch该服务的服务提供方目录(/service/com.demo.xxService/provider)中所有的服务节点数据
  • 当服务提供方目录下有节点数据发生变更时,ZooKeeper就会通知给发起订阅的服务调用方

在这里插入图片描述
ZooKeeper在超大规模下作为注册中心的问题:

  • 随着微服务化程序越来越高,ZooKeeper集群压力将会越来越大,如果有超大批量的服务节点同时发起注册操作,ZooKeeper集群的CPU将突然飙升,导致ZooKeeper集群不能工作,而且ZooKeeper也不能马上重启
  • 原因:根本原因是ZooKeeper本身的性能问题,当连接到ZooKeeper的节点数量特别多,对ZooKeeper读写特别频繁,而且ZooKeeper存储的目录达到一定数量的时候,ZooKeeper将不再稳定,CPU持续升高,最终宕机。而宕机之后,由于各业务节点还在持续发送读写请求,刚一启动,ZooKeeper就因无法承受瞬间的读写压力,马上宕机

那怎么解决呢?

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

我们知道,ZooKeeper的一大特定就是强一致性,ZooKeeper集群的每个节点的数据集每次发生更新操作,都会通知其他ZooKeeper节点同时执行更新。它要求保证每个节点的数据能够实时的完全一致,这也就导致了ZooKeeper集群性能上的下降。这就好比几个人在玩传递东西的游戏,必须这一轮每个人都拿到东西之后,所有的人才能开始下一轮,而不是说我只要获得到东西之后,就可以直接进行下一轮了。

而RPC框架的服务发现,在服务节点刚上线时,服务调用方式可以容忍一段时间后才发现这个新上新的节点的,也就是说我们可以牺牲CP(强一致性)而选择AP(最终一致性),来换取整个注册中心集群的性能和稳定。

那么是否有一种简单、高效,并且最终一致的更新机制,能代替ZooKeeper那种数据强一致性的数据更新机制呢?

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

  • 当有服务上线时,注册中心节点收到注册请求,服务列表数据发生变化,会生成一个消息,推送给消息总线,每个消息都有整体递增的版本
  • 消息总线会主动推送消息到各个注册中心,同时注册中心也会定时拉取消息。对于获取到消息的在消息回放模块里面回放,只接受大于本地版本号的消息,小于本地版本号的消息直接丢弃,从而实现最终一致性
  • 消费者订阅可以从注册中心内存拿到指定接口的全部服务实例,并缓存到消费者的内存里面
  • 采用推拉模式,消费者可以及时的拿到服务实例增量变化情况,并和内存中的缓存数据进行合并

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

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

通过消息总线的方式,我们就可以完成注册中心集群间数据变更的通知,保证数据的最终一致性,并能及时地触发注册中心的服务下发操作

总结

通常我们可以使用 ZooKeeper、etcd 或者分布式缓存(如 Hazelcast)来解决事件通知问题,但当集群达到一定规模之后,依赖的 ZooKeeper 集群、etcd 集群可能就不稳定了,无法满足我们的需求。

在超大规模的服务集群下,注册中心所面临的挑战就是超大批量服务节点同时上下线,注册中心集群接受到大量服务变更请求,集群间各节点间需要同步大量服务节点数据,最终导致如下问题:

  • 注册中心负载过高;
  • 各节点数据不一致;
  • 服务下发不及时或下发错误的服务节点列表。

RPC 框架依赖的注册中心的服务数据的一致性其实并不需要满足 CP,只要满足 AP 即可。我们就是采用“消息总线”的通知机制,来保证注册中心数据的最终一致性,来解决这些问题的。

服务发现的作用就是实时感知集群 IP 的变化,实现接口跟服务集群节点 IP 的映射。在超大规模集群实战中,我们更多需要考虑的是保证最终一致性。其实总结来说,就一关键词,你要记住“推拉结合,以拉为准”。

为什么不用DNS作为服务发现

服务发现的本质就是完成接口跟服务提供者IP的映射。那我们能不能把服务提供IP统一换成一个域名,然后用DNS机制来实现呢?

DNS的流程如下:
在这里插入图片描述
如果我们用DNS来实现服务发现,所有的服务提供者节点都配置在同一个域名下,调用方的确可以通过DNS机制拿到随机的一个服务提供者的IP,并与之建立长连接,这看上去并没有太大问题,但是为什么业界很少用到这种方案呢?问题是:

  • 如果这个IP端口下线了,服务调用方能否及时摘除服务节点呢?
  • 如果在之前已经上线了一部分服务节点,这时突然对这个服务进行扩容,那么新上线的服务节点能否及时接收到流量呢?

这两个问题的答案都是“不能”。

  • 这是因为为了提升性能和减少DNS服务的压力,DNS采用了多级缓存机制
  • 一般配置的缓存时间比较长,所以服务调用者不能及时感知到服务节点的变化

那是不是可以加一个负载均衡设备呢?将域名绑定到这台负载均衡设备上,通过DNS拿到负载均衡的IP。这样服务调用的时候,服务调用方就可以直接跟VIP建立连接,然后由VIP机器完成TCP转发:
在这里插入图片描述
这个方案确实能够解决DNS一些问题,但是在RPC场景里面也不是很合适,原因有如下几点:

  • 搭建负载均衡设备或TCP/IP四层代理,需要额外成本
  • 请求流量都经过负载均衡设备,多经过一次网络传输,会额外浪费性能
  • 负载均衡添加节点和摘除节点,一般都要手动添加,当大批量扩容和下线时,会有大量的人工操作和生效延迟
  • 我们在服务治理的时候,需要更灵活的负载均衡策略,目前的负载均衡算法还满足不了需求

由此可见,DNS或者VIP方案虽然可以充当服务发现的角色,但在RPC场景里面直接用还是很难的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值