概述
NameServer是一个简单的 Topic 路由注册中心,支持 Topic、Broker 的动态注册与发现。
主要包括两个功能:
- Broker管理 ,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
- 路由信息管理 ,每个NameServer将保存关于 Broker 集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

Broker集群、Producer集群、Consumer集群都需要与NameServer集群进行通信。
1、Producer集群
Producer集群和NameServer集群中的随机一台建立⻓连接,得知当前要发送的 Topic 存在哪台BrokerMaster上,然后再与其建立⻓连接,支持多种负载平衡模式发送消息。
注意:消息的生产者,通过NameServer集群获得Topic的路由信息,包括Topic下面有哪些Queue,这些Queue分布在哪些Broker上等。Producer只会将消息发送到Master节点上,因此只需要与Master节点建立连接。
2、Consumer集群
它会先和 NameServer 集群中的随机一台建立⻓连接,得 知当前要消费的 Topic 存在哪台 BrokerMaster、Slave上,然后它们建立⻓连接,支持集群消费和广播消费消息。
消息的消费者,通过NameServer集群获得Topic的路由信息,连接到对应的Broker上消费消息。注意,由于Master和Slave都可以读取消息,因此Consumer会与Master和Slave都建立连接。
3、Broker集群
Broker作用:
- 接收生产者发送消息,或者消费者消费消息的请求。
- 主要负责消息的存储、转发、查询,支持主从部署,一个 Master 可以对应多个 Slave,Master 支持读写,Slave 只支持读。
一个Broker集群由多组Master/Slave组成,Master可写可读,Slave只可以读,Master将写入的数据同步给Slave。
Broker 会向集群中的每一台 NameServer 注册自己的路由信息。每个Broker节点,在启动时,都会遍历NameServer列表,与每个NameServer建立长连接,注册自己的信息,之后定时上报。
4、NameServer集群
Topic 路由注册中心,支持 Broker 的动态注册和发现,保存 Topic 和 Borker之间的关系。
通常是集群部署,但是各 NameServer 之间不会互相通信, 各 NameServer 都有完整的 路由信息
总结:NameServer 相当于一个NettyServer,处理客户端的请求。RocketMQ学习源码之前最好了解一下Netty和NIO通信的基本流程。
NameServer主要功能
1、代码模块

主要有3个模块, kvconfig 数据存储,processor 通信处理模块, routeinfo 路由模块
-
kvconfig 模块 核心类KVConfigManager作用是,加载namesrvController指定的kvConfig配置文件(常xxx/kvConfig.json)到内存,读取或增加,删除kvConfig记录,将内存记录的配置,持久化到文件打印所有kvConfig配置。
-
processor 模块 DefaultRequestProcessor 处理client发过来的各种请求,比如kv 的crud,注册元数据,读取元数据
-
routeinfo 路由模块 RouteInfoManager 存储一些核心元数据
- brokerAddrTable 保存 Topic 的队列信息,也是真正的路由信息。队列信息中包含了其所在的 Broker 名称和读写队列数量。
- topicQueueTable 保存 Broker 信息,包含其名称、集群名称、主备 Broker 地址。
- clusterAddrTable 保存 Cluster信息,包含每个集群中所有的 Broker 名称列表
- brokerLiveTable 活跃Broker映射表,Broker 状态信息,包含当前所有存活的 Broker,和它们最后一次上报心跳的时间。
- filterServerTable Broker 上的 FilterServer 列表,用于类模式消息过滤,该机制在 4.4 版本后被废弃。
上面的这些数据都是存储为HashMap保存的内存中,不会做持久化操作。
2、NameServer启动流程
NamesrvStartup 启动类, 逻辑并不复杂,主要是下面两个主要功能。
(1)解析配置文件
填充NameServerConfig、NettyServerConfig属性值。包括:端口号,目录路径,netty线程池个数,消息请求并发度等。
(2) 根据启动属性,创建NamesrvController实例,并初始化该实例。NameServerController为nameServer的核心控制器。
NameServerController里面有一个nettyServer和两个定时任务。
- nettyServer负责与broker、生产者、消费者进行网络通信
- 定时任务1:每隔10s扫描一次broker,移除死亡状态broker。
- 定时任务2:每隔10分钟打印一次KV配置。
3、路由注册
RocketMQ采取的策略是,在Broker节点在启动的时候,轮询NameServer列表,
(1)Broker与每个NameServer节点建立长连接,发起注册请求。
(2)Broker节点为了证明自己是存活的,会将最新的信息上报给NameServer,然后每隔30秒向NameServer发送心跳包。
心跳包中包含 BrokerId、Broker名称、Broker地址、Broker所属集群名称等等。
NameServer独立管理自己的Broker元数据副本:
(1)每个NameServer内部会维护一个Broker表,用来动态存储Broker的信息。
(2)然后NameServer接收到心跳包后,会更新时间戳,记录这个Broker的最新存活时间
路由注册的两个场景:
- 启动注册:Broker与每个NameServer节点建立长连接,发起注册请求
- 定时心跳:每隔30秒向NameServer发送心跳包
设计的亮点:
NameServer的性能优化:
为了提升并发修改Broker表的性能,路由注册操作引入了 ReadWriteLock 读写锁,这个设计亮点允许多个消息生产者并发读,保证了消息发送时的高并发,同一时刻NameServer只能处理一个Broker心跳包,多个心跳包串行处理。
这也是读写锁的经典使用场景,即读多写少。
4、路由剔除
NameServer路由剔除的两个场景:
- 主动剔除场景:Broker在正常关闭的情况下,会执行unregisterBroker指令
- 被动剔除场景:NameServer定期扫描brokerLiveTable检测上次心跳包与当前系统的时间差,如果时间超过120s,则需要移除broker。
3.1 主动剔除的场景
nameServer与broker保持长连接。
正常情况下,如果Broker关闭,则会与NameServer断开长连接,Netty的通道关闭监听器会监听到连接断开事件,然后会将这个Broker信息剔除掉。
对于一些日常运维工作,例如:Broker升级,RocketMQ提供了一种优雅剔除路由信息的方式。
如在升级一个节Master点之前,可以先通过命令行工具,禁止这个Broker的写权限:
客户端producer 发送消息到这个Broker的请求,都会收到一个NO_PERMISSION响应,客户端会自动重试其他的Broker。
当观察到这个broker没有流量后,再将这个broker移除。
3.2 被动剔除的场景
rocketMq的路由注册是通过broker与NameServer的心跳功能实现的。
broker启动的时候,向集群中的所有nameServer发送心跳,每间隔30秒,向所有的NameServer发送心跳。
nameServer收到心跳包时会更新brokerLiveTable缓存中的lastUpdateTimestamp。然后nameServer每10秒扫描一次brokerLiveTable,如果连续120s没有收到心跳包,判死刑,nameServer将移除该Broker的路由信息,同时关闭nettysocket连接。
被动剔除下,NameServer中有一个定时任务, 每隔 10秒扫描一下Broker表,如果某个Broker的心跳包最新时间戳距离当前时间超多120秒,也会判定Broker失效并将其移除。
5、路由发现
路由发现是客户端的行为,这里的客户端主要说的是生产者和消费者。
主动刷新场景:
(1)对于生产者,在发送第一条消息时,才能明确topic,所以会根据Topic获取从NameServer获取路由信息。
(2)对于消费者,订阅的Topic一般是固定的,所在在启动时就会拉取。
被动刷新的场景:
定时刷新机制
6、客户端NameServer选择策略
RocketMQ会将用户设置的NameServer列表会设置到 NettyRemotingClient类的namesrvAddrList字段中,NettyRemotingClient是RocketMQ对Netty进行了封装,如下:

具体选择哪个NameServer,也是使用round-robin的策略。需要注意的是,尽管使用round-robin策略,但是在选择了一个NameServer节点之后,后面总是会优先选择这个NameServer,除非与这个NameServer节点通信出现异常的情况下,才会选择其他节点。
为什么客户端不与所有NameServer节点建立连接呢,而是只选择其中一个?
通常NameServer节点是配置好的,固定的那么几个,但是客户端的数量可能是成百上千,为了减少每个NameServer节点的压力,所以每个客户端节点开始的时候,随机找一个nameserver建立连接,后面round-robin与其中一个NameServer节点建立连接。
为了尽可能保证NameServer集群每个节点的负载能相对均衡,在round-robin策略选择时,每个客户端的初始随机位置都不同。
NettyRemotingClient#namesrvIndex
private final AtomicInteger namesrvIndex = new AtomicInteger(initValueIndex());
private static int initValueIndex() {
Random r = new Random();
return Math.abs(r.nextInt() % 999) % 999;
}
之后每次选择NameServer时,namesrvIndex+1之后,对namesrvAddrList取模,计算在数据下标的位置,尝试创建连接,一旦创建成功,会将当前选择的NameServer地址记录到namesrvAddrChoosed字段中:
private final AtomicReference<String> namesrvAddrChoosed = new AtomicReference<String>();
如果某个NameServer节点创建连接失败是,会自动重试其他节点。
NettyRemotingClient#getAndCreateNameserverChannel
7、NameServer的CAP特征:是AP不是CP?
那么为什么rocketmq选择自己开发一个NameServer,而不是使用这些开源组件呢?
事实上,在RocketMQ的早期版本,即MetaQ 1.x和MetaQ 2.x阶段,也是依赖Zookeeper的。但MetaQ 3.x(即RocketMQ)却去掉了ZooKeeper依赖,转而采用自己的NameServer。
而RocketMQ的架构设计决定了:
nameserver只需要一个轻量级的元数据服务器就足够了nameserver只需要保持最终一致,而不需要Zookeeper这样的强一致性解决方案,不需要再依赖另一个中间件,从而减少整体维护成本。

对于Zookeeper、Etcd这样强一致性组件,数据只要写到主节点,内部会通过状态机将数据复制到其他节点,
Zookeeper使用的是Zab协议,etcd使用的是raft协议。
但是:NameServer节点之间是互不通信的,无法进行数据复制。
网友评论