在生产环境中服务提供方一般以集群的方式对外提供服务,通过“通信录”获取到对应的服务节点,这个获取的过程叫作“服务发现”。
服务调用方和服务提供方来说,其契约就是接口,服务节点就是提供该契约的一个具体实例。
- 服务注册:在服务提供方启动的时候,将对外暴露的接口注册到注册中心之中,注册中心将这个服务节点的IP和接口保存下来。
- 服务订阅:在服务调用方启动的时候,去注册中心查找并订阅服务提供方的IP,然后缓存到本地,并用于后续的远程调用。
基于ZooKeeper的服务发现
- 服务平台管理端先在ZooKeeper中创建一个服务根路径,可以根据接口名命名(例如:/service/com.demo.xxService),在这个路径再创建服务提供方目录与服务调用方目录(例如:provider、consumer),分别用来存储服务提供方的节点信息和服务调用方的节点信息。
- 当服务提供方发起注册时,会在服务提供方目录中创建一个临时节点,节点中存储该服务提供方的注册信息。
- 当服务调用方发起订阅时,则在服务调用方目录中创建一个临时节点,节点中存储该服务调用方的信息,同时服务调用方watch该服务的服务提供方目录(/service/com.demo.xxService/provider)中所有的服务节点数据。
- 当服务提供方目录下有节点数据发生变更时,ZooKeeper就会通知给发起订阅的服务调用方。
由于ZooKeeper的一大特点强一致性
,ZooKeeper集群的每个节点的数据每次发生更新操作,都会通知其它ZooKeeper节点同时执行更新(CP)。
ZooKeeper本身性能问题,当连接到ZooKeeper的节点数量特别多,对ZooKeeper读写特别频繁,且ZooKeeper存储的目录达到一定数量的时候,ZooKeeper将不再稳定,CPU持续升高,最终宕机。而宕机之后,由于各业务的节点还在持续发送读写请求,刚一启动,ZooKeeper就因无法承受瞬间的读写压力,马上宕机。
RPC框架的服务发现,在服务节点刚上线时,服务调用方是可以容忍在一段时间之后(比如几秒钟之后)发现这个新上线的节点的。毕竟服务节点刚上线之后的几秒内,甚至更长的一段时间内没有请求流量,对整个服务集群是没有什么影响的,可以牺牲掉CP(强制一致性),而选择AP(最终一致)
,来换取整个注册中心集群的性能和稳定性。
基于消息总线的最终一致性的注册中心
要求最终一致性,可以考虑采用消息总线机制。
注册数据可以全量缓存在每个注册中心内存中,通过消息总线来同步数据。当有一个注册中心节点接收到服务节点注册时,会产生一个消息推送给消
息总线,再通过消息总线通知给其它注册中心节点更新数据并进行服务下发,从而达到注册中心间数据最终一致性,具体流程如下图所示:
- 当有服务上线,注册中心节点收到注册请求,服务列表数据发生变化,会生成一个消息,推送给消息总线,每个消息都有整体递增的版本。
- 消息总线主动推送消息到各个注册中心,同时注册中心也会定时拉取消息。对于获取到消息的在消息回放模块里面回放,只接受大于本地版本号的消息,小于本地版本号的消息直接丢弃,从而实现最终一致性。
- 消费者订阅可以从注册中心内存拿到指定接口的全部服务实例,并缓存到消费者的内存里面。
- 采用推拉模式,消费者可以及时地拿到服务实例增量变化情况,并和内存中的缓存数据进行合并。
为了性能,这里采用了两级缓存,注册中心和消费者的内存缓存,通过异步推拉模式来确保最终一致性。
服务发现的特性是允许设计超大规模集群服务发现系统的时候,舍弃强一致性,更多地考虑系统的健壮性。最终一致性才是分布式系统设计中更为常用的策略。