注册中心ZK、nameServer、eureka、Nacos介绍与对比

Posted by zhangtao on Tuesday, October 31, 2023

前言

注册中心的由来

微服务架构是存在着很多跨服务调用,每个服务都存在着多个节点,如果有多个提供者和消费者,当提供者增加/减少或者消费者增加/减少,双方都需要感知发现。所以诞生了注册中心这个中间件。

市面上有很多注册中心,如 Zookeeper、NameServer、Eureka、Nacos,下面我来讲一下它们的特点、应用和区别。

Zookeeper

Zookeeper的存储结构是树形结构,它有四种节点,分别是:

  • 持久节点:除非自己删除,否则一直存在。
  • 持久顺序节点:加了编号,按添加时间排序。
  • 临时节点:Zookeeper会维护一个跟客户端的session,通过心跳存续,如果客户端失去心跳,一段时间后节点的session到期,就会删除节点。
  • 临时顺序节点。

特点

  • Watch监听器:当客户端向某个节点添加监听,当节点发生变化,Zookeeper会实时通知客户端。
  • 节点的名字唯一,不允许重复创建。

强一致性

Zookeeper多节点部署,只要集群中存在超过一半的节点能够正常工作,那么整个集群就能够正常对外服务。

Zookeeper围绕着ZAB协议保障数据的一致性。

ZAB协议里规定,Zookeeper集群中只有一个主节点,其余都是从节点。

所有的写请求都必须先走主节点,主节点写入后,同步给从节点,超过半数的节点返回成功,则返回客户端成功,没有超过一半,则返回客户端失败。

为了提升读的性能,读请求不要求必须请求主节点,从节点也可以读。

如果主节点挂了,那么会进行主节点选举,ZAB协议为了保障一致性,选举期间服务是不可用的,牺牲了一些可用性(CP)。

当主节点挂了,就会开始选举,持有消息最新的节点有资格参加竞选,当最终投票超过半数就会被选为主节点,并通知其他节点。

应用

利用上述这些特点,Zookeeper有用广泛的应用。

Dubbo中的注册中心

当Dubbo provider启动时,会在Zookeeper上的 /dubbo/{serviceName}/providers 节点上添加一个临时节点。

当consumer启动时,会在Zookeeper上的 /dubbo/{serviceName}/consumers 节点下添加一个临时节点,同时添加watcher监听providers节点。

当新增provider节点,consumer通过watcher机制能够马上会收到并本地缓存。

当provider挂了,心跳断开连接时,等临时节点的会话到期会触发节点删除,consumer会收到并本地缓存。

通过watcher机制,当consumer发生了变化,provider能够及时感应到。

Zookeeper

分布式锁

加锁就是在Zookeeper上添加临时顺序节点,判断是否是最小节点,如果是最小节点,则无需排队。如果不是最小的,则往后加一个顺序节点,并且向前一个节点添加一个watch监听,线程等待排队,当上一个节点删除后,会通知唤醒这个线程。

通过watch机制唤醒线程的方式比较优雅,避免了羊群效应。

Kafka的应用

Broker集群各节点之间独立,使用Zookeeper将整个集群中的Broker统一管理。

Topic消息会被分到多个partition并将其分布在多个Broker上,Topic与Broker的对应关系也都是由Zookeeper在维护。

每个partition只能被同组的某一个Consumer进行消费,因此需要在Zookeeper上记录partition与Consumer之间的对应关系。

早期Kafka多副本选举,leader故障后Zookeeper检测到会通知其他副本节点(只有isr副本才有资格)进行选举,谁先创建leader节点,就成为leader,通知其他副本watch新的leader节点。

NameServer

NameServer是RocketMQ中重要的组成部分。它跟Kafka当中的Zookeeper的角色类似,充当了注册中心的作用。

应用

Broker启动后,会向每一台NameServer节点注册信息,并会与NameServer维持一个心跳,当Broker故障后,心跳丢失,NameServer会将Broker踢出。

创建topic时,需要绑定Broker队列,创建成功后会向NameServer注册topic与Broker的对应关系。

NameServer

Producer启动后,会从NameServer中获取topic对应的Broker信息,从而知道自己订阅的topic信息要发送到哪些Broker上,根据负载均衡算法选择1台Broker建立连接,进行消息发送。

Consumer启动后,会从NameServer中获取topic对应的Broker信息,从而知道自己订阅的topic信息存在于哪些Broker上,然后跟相关的Broker建立连接,消费消息。

高可用

NameServer采用的是跟Zookeeper不一样的模式,它是无状态、去中心化的中间件。

作为注册中心,一致性的要求没有这么强。但是需要保障可用性,每个NameServer节点数据可以不一致,也不需要保障一半以上的节点存活,只要有一台节点存活,集群就可以正常运作.

NameServer High Availability

下面是NameServer的部分源码,也可以明了的看出NameServer所维护的数据。

// NameServer源码
public class RouteInfoManager {
    private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.NAMESRV_LOGGER_NAME);
    private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    // topic与Broker的绑定关系
    private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
    private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
    private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
    private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
    private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
}

eureka

也是无状态、去中心化的中间件,AP模式

eureka有一个eureka还有一种自我保护机制,如果在15分钟内超过 85% 的节点都没有正常的心跳,那么就会进入自我保护机制,不再下线节点。这里会有坑,如果服务器真的大面积故障了,但是 eureka上无法下线,就仍然会命中故障机。这时候需要设置enableSelfPreservation=false

Nacos

Nacos是阿里巴巴推出的新一代的集注册中心和配置中心一体的中间件

注册Nacos的节点跟注册zk的节点一样,也有临时节点和永久节点之分,当节点故障后,被Nacos的心跳检测到断连后,如果是临时节点,这个注册的节点会被Nacos删除。如果是永久节点,不会被删除,只会被标记成不健康。Nacos作为服务注册中心时,适合注册临时节点。如果是一些基础服务组件,例如数据库、缓存等更适合永久节点注册

Nacos的特点是提供AP和CP两种模式给开发者使用,并且可以无缝切换。需要强一致的时候使用CP,是用raft协议实现的。如果是要保证高可用不需要强一致,可以使用AP模式(借鉴了eureka)

此外Nacos还支持动态配置

总结

这是上面几种中间件作为注册中心的对比

中间件 一致性模型 特点 适用场景
Zookeeper CP 强一致性、有序节点、持久性 Dubbo注册中心、分布式锁、Kafka
NameServer AP 轻量级、无状态 RocketMQ注册中心
Eureka AP AP模型、自我保护机制 微服务、高可用应用
Nacos AP/CP 支持AP和CP模型、动态配置和监听机制 微服务、配置中心、注册中心

当我们考虑注册中心选型时,CAP问题无法避免,上面这些中间件无非就是这两种模式中的一种

  • 基于 Leader 的非对等部署的单点写⼀致性 CP
  • 对等部署的多写⼀致性 AP

当我们选用服务注册中心的时候,可用性比一致性更重要,同时CP这种单Leader节点在高并发的场景会承受更多的压力,会存在性能瓶颈,所以Zookeeper是不太适合作为注册中心的