RocketMQ NameServer源码剖析

概述

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作用:

  1. 接收生产者发送消息,或者消费者消费消息的请求。
  2. 主要负责消息的存储、转发、查询,支持主从部署,一个 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 存储一些核心元数据

  1. brokerAddrTable 保存 Topic 的队列信息,也是真正的路由信息。队列信息中包含了其所在的 Broker 名称和读写队列数量。
  2. topicQueueTable 保存 Broker 信息,包含其名称、集群名称、主备 Broker 地址。
  3. clusterAddrTable 保存 Cluster信息,包含每个集群中所有的 Broker 名称列表
  4. brokerLiveTable 活跃Broker映射表,Broker 状态信息,包含当前所有存活的 Broker,和它们最后一次上报心跳的时间。
  5. 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节点之间是互不通信的,无法进行数据复制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值