之家云在注册中心选型上的探索与实践

993 阅读12分钟

什么是注册中心

在云原生场景下,注册中心主要用于服务注册和服务发现,以实现微服务(Microservice,服务网格内的服务)的负载均衡和故障转移。 一般来说注册中心还会提供一个客户端组件,使得客户端和服务端交互更加容易,并且我们可以使用注册中心客户端组件提供基于流量(traffic)、资源使用(resource usage)、区域(Region)等更加复杂的负载均衡器,从而使我们的应用在云上具有更好的弹性。

为什么需要注册中心

在云上(k8s),大多数应用都是“朝生夕死”,传统的负载均衡器(如nginx)使用的具名的ip和host,这在云上显然是不适用的,因为我们不可能在应用每次发布之后都重新修改一下nginx的upstream配置并重新reload,所以在云上要求负载均衡器可以动态的感知应用的ip。

在k8s中,service同样提供了负载均衡和服务发现的功能,那么servcie和注册中心有什么区别那?service的服务发现是基于dns的,负载均衡功能基于iptables。相对于注册中心来说,service的负载均衡和故障转移能力比较弱,但是基于注册中心客户端可以实现更加复杂的负载均衡器,比如在负载均衡客户端缓存可用应用的信息,使服务具有更好的弹性。另外iptables和dns在服务规模扩大之后会有性能问题,而注册中心则可以通过横向扩展解决。

当然我们也可以将微服务,直接暴露在ingress后面,但是这意味着将“网格”内的服务暴露给了外界,这显然需要更加严格的安全策略来保证网格内服务的安全。利用注册中心我们可以实现客户端负载均衡,这相对服务端负载均衡(如nginx)显著的优势是可以减少一个额外的“网络跃点”,进而减少延迟。

注册中心和DNS的区别

注册中心用于实现区域内的服务发现和负载均衡,注册中心类似于一个内部的DNS,不同区域的注册中心是相互隔离的。

DNS也可以用作服务发现和负载均衡,它的优势在于跨语言,不过传统DNS负载均衡的缺点是流量可能路由到不健康的或者是已经下线的实例上去,因为DNS需要缓存。

注册中心选型

从一致性协议上来看,注册中心基本上可以分为两种:

  • 基于Paxos的一致性系统。特点是有主、单点写、数据一致性能力强。例如基于Raft协议的Etcd、基于Zab协议Zookeeper。
  • 对等部署的、无主的、多写一致性系统,例如Nacos和Eureka。

Paxos的一致性系统

服务的规模

Paxos的一致性系统,为了保证数据的最终一致性,只能由Leader节点执行事务性操作。这导致了他们没有办法通过水平扩展来增加注册中心的写性能,对于服务规模小于2k+的场景它们可以应付,但是随着服务规模的扩大,尤其在微服务架构流行的今天,服务规模随着业务的发展出现爆发式的增长,类似于dubbo这种以接口维度作为服务的服务治理框架,所有服务的心跳检查都要转发到leader节点处理,并且在云上应用进行滚动更新的时候,对注册中心带来的写请求压力是巨大的。

可用性以及容灾

在服务规模扩大之后,我们自然而然会想到在多个地域(Region)的多个可用区(Availability Zone,AZ) 内部署注册中心,以提高集群的可用性。比如在华北地区的多个两个可用区内部署,如下图:

image.png

当AZ-A和AZ-B发发生网络分区的时候,Paxos的一致性系统为了解决集群脑裂和保证数据一致性会挂起事务性操作,这导致了AZ-A(日志复制投票不能过半)和AZ-B(无法选举出Leader)都不能注册、下线新的服务,服务间的正常通信需要依赖服务治理框架(如dubbo会在本地缓存服务列表和节点的健康检查)提供容灾能力。

事务日志和快照

Paxos的一致性系统,在向Leader提交命令后,会在磁盘记录事务日志,用于向 Follow 节点复制数据以保证集群间数据的一致性,并且每隔一段时间会向磁盘更新内存中数据的快照,用于节点崩溃后的数据恢复。

当Paxos的一致性系统集群的规模变大,集群中 Follow 服务器数量逐渐增多的时候,集群处理创建数据节点等事务性请求操作的性能就会逐渐下降。这是因为集群在处理事务性请求操作时,要在集群中对该事务性的请求发起投票,只有超过半数的 Follow 服务器投票一致,才会执行该条写入操作,然后向客户端做出响应。

基于以上分析,Paxos的一致性系统存在以下问题:

  • Leader的单点写入问题,在服务的规模很大的时候会制约注册中心的写入能力。
  • 无法横向扩展,无法跨地域部署,不能简单的通过增加节点数量,提高集群的可用性和写入性能。
  • 由于日志复制需要投票,集群规模受到制约,尤其是在跨地域场景下会导致客户端写入响应时间增加。
  • 网络分区后,注册中心不能提供服务。

Paxos的一致性系统如何部署高可用集群

为了解决随着集群规模的扩大集群写入性能变差的问题,通过引入 Learner 节点(例如zk中observer、etcd的raft learner) 角色,即只进行数据同步而不参与多数派投票的角色,来避免直接多节点部署的投票延时问题。

image.png

基于此架构,利用dubbo的多注册中心机制通过同城多活和 Learner 做跨地域数据同步来保证较高的可用性和较高的写入效率。

Eureka

每个区域(Region)中都部署有一个Eureka集群,每个区域只负责该区域内的实例,每个区域的每个可用区(Zone)中至少有一个Eureka Server处理可用区内的故障。

Eureka客户端会尝试和相同可用区内的 Eureka Server 进行通信。如果无法和Server通信或者和Server不在同一可用区内,则客户端会尝试其他可用区内Server。

服务向 Eureka 进行注册,然后每隔30秒发送心跳更新租约。如果客户端在一段时间内不能更新租约,那么服务的实例将在大约90s内从 Eureka Server 注册表中删除。

Eureka Server 开始接收流量后,在Server上执行的所有操作(服务的注册,下线,修改,心跳)都将复制到服务器知道的所有对等节点。如果某个操作由于某种原因失败,那么该信息将在下一个心跳进行协调,该心跳也将在Server之间复制。

当 Eureka Server 启动时,它将尝试从相邻节点获取所有实例注册表信息。如果从节点获取信息有问题,则服务器会在会尝试所有对等节点。

服务注册信息和更新信息会被复制到Eureka 集群中的每一个节点。来着任意一个可用区的客户端都可以查找到位于任意可用区服务的注册信息,并进行远程调用。

节点的自我保护模式

连续3次心跳续订失败的所有客户端将被视为不正常的终止,并且将由后台进行驱逐。如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制。

网络分区

当 Eureka 对等节点发生网络故障的时候,可能会发生以下情况:

  • 对等节点间的心跳复制可能会失败,并且服务器会检测到这种情况,并进入自我保护模式以保护当前状态。
  • 服务注册和下线可能发生在孤立的 Server上(可用区内),这时在不同可用区内的客户端上服务的实例列表可能会不同。
  • 网络连接恢复到稳定状态后,情况会自动更正。当对等方能够正常通信时,注册信息会自动协调到没有的对等节点。

通过以上分析可以了解到 Eureka 在发生网络分区后不会影响集群的可用性,在同一可用区内服务可以正常的注册和下线,在不同可用区内就需要依赖服务治理框架来保证流量不被转发到已下线的服务上区。

Eureka的多级缓存

image.png

Eureka 使用多级缓存机制减少注册表的读写冲突问题并尽可能的增加 Eureka Server的吞吐量。

Eureka存在的问题

  • Eureka客户端使用询模型: 定期从服务器获取更新的注册信息。即使不进行任何更改,这也会给客户端造成按计划对服务器进行调用的开销,更重要的是由于轮训模型和缓存将导致更新不会及时的被客户端感知(最长的情况下将有200+s)。
  • 复制算法限制了集群的可伸缩性: Eureka遵循广播复制模型,即所有服务器将数据和心跳复制到所有对等节点。这对于Eureka包含的数据集既简单又有效,但是复制是通过将服务器接收的所有HTTP调用照原样转发给所有对等方来实现的。由于每个节点必须承受Eureka上的整个写入负载,因此这限制了集群的可伸缩性。
  • 只支持同构的客户端视图: Eureka服务器只期望客户端总是获得整个注册表,不允许获得特定的应用程序/VIP地址。这对所有注册到Eureka的客户端施加了内存限制,即使他们需要Eureka注册表的一个非常小的子集。

Eureka 2.0

Eureka 2.0设计了一种读写分离的架构,主要优化复制算法和支持客户端端订阅模型,遗憾的是Eureka 2.0的开源工作已经停止了。但是Eureka 2.0提供了一种很好的思路。这个思路被SofaRegistry实现了,并且开源了,由于SofaRegistry在国内落地的不多,所以本篇文章暂不讨论。

Nacos

image.png

Nacos没有使用读写分离的架构,但是相对于Eureka来说重点优化了集群间的复制算法支持订阅模型的功能。

  • 采用分片机制,每一个节点负责一部分数据。
  • 定时进行增量复制。
  • 使用copyOnWrite保证数据一致性,相对于Eureka的多级缓存数据实效性更强,但是copyOnWrite更加占用内存,不过注册中心的场景是读多写少。
  • 使用udp进行服务变更推送。

但是Nacos也不是没有缺点,因为它同样采用TTL的方式进行服务续约,服务失效后依然会客户端感知依然存在一定的延时。另外由于心跳包含服务的所有元数据,所以在像dubbo这种以接口为维度的大规模服务治理场景会导致集群的TPS比较高和更多的网络负载。

总结

综上述,每一种类型的注册中心都有各自的优点和缺点,在微服务落地的过程中,注册中心的主要挑战来自于以下几点:

  • 跨地域部署(同城双活、三地五中心),集群扩展问题。
  • 服务规模扩大,注册中心内存压力、推送量大。

我们需要根据不同场景、服务规模、服务治理框架选择合适的注册中心。结合之家云目前微服务落地的实际情况,我们目前争对dubbo和spring cloud体系使用了不同的注册中心。

dubbo服务

ZK + Observer的同城双活的高可用部署。原因如下:

  • 从dubbo诞生至今经历了十年的发展,对zk注册中心的支持可以说是最好的,虽然zk的客户端比较使用起来比较复杂,但是社区也在一直投入人力进行优化和升级。
  • dubbo的多注册中心模式,完美了支持了这种部署架构,
  • 传统的dubbo服务是以接口为粒度的,目前之家云微服务已经管理了 3000+ 规模的dubbo服务,每个应用大概有 20~100 dubbo服务,使用nacos的话每个应用服务要管理 20~100的定时任务发处理心跳,并且这个操作是不可评估的,所以反而不如使用zk。
  • 针对k8s滚动升级的场景下,导致注册中心推送量大的场景,我们也做了相应的优化,后面在之dubbo柔性服务上的建设和实践中我们会单独介绍。

spring cloud

Nacos 同城双活架构。原因如下:

  • 同Region多可用区内网络延迟较低,数据同步快。
  • 由于spring cloud 不支持多注册中心,所以zk的同城双活架构无法被spring cloud 支持。
  • spring cloud 已应用粒度作为服务,就不存在上述dubbo服务中的问题,因为我们对应用的扩缩容都是可以评估的。
  • Nacos Sync组件,可以良好的让我们实现跨地域(region)的数据同步和数据迁移。

参考: