服务注册与发现

4 minute read

  1. Zookeeper保证CP 当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。 但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。 问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。 在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

2.Eureka保证AP Eureka看明白了这一点,因此在设计时就优先保证可用性。 Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。 而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。 除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
  2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
  3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。

注册中心的一般流程是: 首先,在服务启动时,服务提供者会向注册中心注册服务,暴露自己的地址和端口等,注册中心会更新服务列表。 服务消费者启动时会向注册中心请求可用的服务地址,并且在本地缓存一份提供者列表,这样在注册中心宕机时仍然可以正常调用服务。 如果提供者集群发生变更,注册中心会将变更推送给服务消费者,更新可用的服务地址列表。

服务注册和发现需要考虑的几个问题: 服务生产者需要注册哪些信息?什么时候去注册?服务实例怎么进行健康检测?服务动态扩缩容怎么同步? 服务消费者怎么获取服务生产者地址?生产者扩缩容怎么同步给消费者?注册中心不可用怎么办?

服务生产者在启动时会指定自己的服务端口,像springboot框架中,如果服务正常启动后,会通过事件的形式通知容器启动完毕,如果服务下线时,可以通过 Java语言的JVM Shutdown Hook方式来实现服务下线后通知注册中心的一些后处理操作。

健康检查: 服务端侧健康检查: 客户端每隔一定时间主动发送“心跳”的方式来向服务端表明自己的服务状态正常,心跳可以是 TCP 的形式,也可以是 HTTP 的形式。

也可以通过维持客户端和服务端的一个 socket 长连接自己实现一个客户端心跳的方式。

ZooKeeper 并没有主动的发送心跳,而是依赖了组件本身提供的临时节点的特性,通过 ZooKeeper 连接的 session 来维持临时节点。

怎么找到服务发现服务端的地址? 在应用的配置文件中指定服务注册中心的地址,类似于 zookeeper 和 eureka。 指定一个地址服务器的地址,然后通过这个地址服务器来获取服务注册中心的地址,地址服务器返回的结果会随着服务注册中心的扩缩容及时更新。

当服务节点数越来越多时,服务注册中心的性能会成为瓶颈,这时候就需要通过水平扩容来提升服务注册中心集群的性能。

对于那些采用了类 Paxos 协议的强一致性的组件,如ZooKeeper,由于每次写操作需要过半的节点确认。水平扩容不能提升整个集群的写性能,只能提升整个集群的读性能。 而对于采用最终一致性的组件来说,水平扩容可以同时提升整个集群的写性能和读性能。

客户端容灾策略 首先,本地内存缓存,当运行时与服务注册中心的连接丢失或服务注册中心完全宕机,仍能正常地调用服务。

然后,本地缓存文件,当应用与服务注册中心发生网络分区或服务注册中心完全宕机后,应用进行了重启操作,内存里没有数据,此时应用可以通过读取本地缓存文件的数据来获取到最后一次订阅到的内容。

最后,本地容灾文件夹。正常的情况下,容灾文件夹内是没有内容的。当服务端完全宕机且长时间不能恢复,同时服务提供者又发生了很大的变更时,可以通过在容灾文件夹内添加文件的方式来开启本地容灾。此时客户端会忽略原有的本地缓存文件,只从本地容灾文件中读取配置。 服务端容灾与高可用 当有新节点加入集群时,节点启动后能自动添加到地址服务器中,并通过地址服务器找到其他节点,自动从其他节点同步数据,以达到数据的最终一致性。 当某个节点宕机时,此服务注册中心节点的信息会自动地址服务器中摘除,客户端能及时感知到此节点已下线。 服务端的无状态性保证了服务的容灾和高可用可以做的很薄。

ZooKeeper 主要应用在 Dubbo 的注册中心实现,由于 Dubbo 在国内的流行,Dubbo + ZooKeeper 的典型服务化方案,使得 ZooKeeper 成为注册中心的经典解决方案。

zookeeper实现了一个类似于文件系统的树状结构,这些节点被称为znode,每个节点可以存放一定的数据 最主要的是 znode 有四种类型:

永久节点(除非手动删除,节点永远存在) 永久有序节点(按照创建顺序会为每个节点末尾带上一个序号如:root-1) 瞬时节点(创建客户端与 Zookeeper 保持连接时节点存在,断开时则删除并会有相应的通知) 瞬时有序节点(在瞬时节点的基础上加上了顺序)

zookeeper怎么实现实时更新服务生产者的信息? 利用zookeeper瞬时节点的特性 Zookeeper 是一个典型的观察者模式。 由于瞬时节点的特点,我们的消费者可以订阅瞬时节点的父节点。 当新增、删除节点时所有的瞬时节点也会自动更新。 更新时会给订阅者发起通知告诉最新的节点信息。

这样我们就可以实时获取服务节点的信息,同时也只需要在第一次获取列表时缓存到本地;也不需要频繁和 Zookeeper 产生交互,只用等待通知更新即可。

并且不管应用什么原因节点 down 掉后也会在 Zookeeper 中删除该信息。

ZooKeeper ZooKeeper 主要应用在 Dubbo 的注册中心实现,由于 Dubbo 在国内的流行,Dubbo + ZooKeeper 的典型服务化方案,使得 ZooKeeper 成为注册中心的经典解决方案。

ZooKeeper 是一个树形结构的目录服务,支持变更推送。使用 ZooKeeper 实现服务注册,就是应用了这种目录结构。

服务提供者在启动的时候,会在 ZooKeeper 上注册服务。以 com.dubbo.DemoService 为例,注册服务,其实就是在 ZooKeeper 的 /dubbo/com.dubbo.DemoService/providers 节点下创建一个子节点,并写入自己的 URL 地址,这就代表了 com.dubbo.DemoService 这个服务的一个提供者。

服务消费者在启动的时候,会向 ZooKeeper 注册中心订阅服务列表,就是读取并订阅 ZooKeeper 上 /dubbo/com.dubbo.DemoService/providers 节点下的所有子节点,并解析出所有提供者的 URL 地址来作为该服务地址列表。

Eureka 在 Spring Cloud 中,提供了 Eureka 来实现服务发现功能。Eureka 采用的是 Server 和 Client 的模式进行设计,Eureka Server 扮演了服务注册中心的角色,为 Client 提供服务注册和发现的功能。

Eureka Client 通过客户端注册的方式暴露服务,通过注解等方式嵌入到服务提供者的代码中,当服务启动时,服务发现组件会向注册中心注册自身提供的服务,并周期性地发送心跳来更新服务。

如果连续多次心跳不能够发现服务,那么 Eureka Server 就会将这个服务节点从服务注册表中移除,各个服务之间会通过注册中心的注册信息来实现调用。

对于服务注册和发现场景来说,一般认为,可用性比数据一致性更加重要。 针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不相同,会出现部分提供者地址不存在等,不会导致严重的服务不可用。 对于服务消费者来说,能消费才是最重要的,拿到可能不正确的服务实例信息后尝试消费,也要比因为无法获取实例信息而拒绝服务好。

临时节点+长连接 在 zookeeper 中存在持久化节点和临时节点的概念。 持久化节点一经创建,只要不主动删除,便会一直持久化存在; 临时节点的生命周期则是和客户端的连接同生共死的,应用连接到 zookeeper 时创建一个临时节点,使用长连接维持会话,这样无论何种方式服务发生下线,zookeeper 都可以感知到,进而删除临时节点。 zookeeper 的这一特性和服务下线的需求契合的比较好,所以临时节点被广泛应用。

主动下线+心跳检测 并不是所有注册中心都有临时节点的概念,另外一种感知服务下线的方式是主动下线。 例如在 eureka 中,会有 eureka-server 和 eureka-client 两个角色,其中 eureka-server 保存注册信息,地位等同于 zookeeper。 当 eureka-client 需要关闭时,会发送一个通知给 eureka-server,从而让 eureka-server 摘除自己这个节点。 但这么做最大的一个问题是,如果仅仅只有主动下线这么一个手段,一旦 eureka-client 非正常下线(如断电,断网),eureka-server 便会一直存在一个已经下线的服务节点,一旦被其他服务发现进而调用,便会带来问题。 为了避免出现这样的情况,需要给 eureka-server 增加一个心跳检测功能,它会对服务提供者进行探测,比如每隔30s发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线。

一般而言注册中心的特性决定了其使用的场景,例如很多框架支持 zookeeper,在我自己看来是因为其老牌,易用, 但业界也有很多人认为 zookeeper 不适合做注册中心,它本身是一个分布式协调组件,并不是为注册服务而生,server 端注册一个服务节点,client 端并不需要在同一时刻拿到完全一致的服务列表,只要最终一致性即可。 在跨IDC,多数据中心等场景下 consul 发挥了很大的优势,这也是很多互联网公司选择使用 consul 的原因。 eureka 是 ap 注册中心,并且是 spring cloud 默认使用的组件,spring cloud eureka 较为贴近 spring cloud 生态。

Updated:

Leave a comment