RocketMQ源码解读之NameServer路由机制

一个得不到执行的念头只会消亡。

                           ——罗杰.冯.欧克

大纲

图示

    源码分析的内容比较多,当然我们不可能把 RocketMQ 所有的源码都读完,所以我们把核心、重点的源码进行解读。 

本节思考:

    > NameServer、Broker、Producer、Consumer的连通性

    > Producer、Consumer连接的建立时机,有何关系?

    > NameServer存储哪些信息,如何存储?

    > Topic的持久化存储是在NameServer中还是在Broker?

RocketMQ整体架构及连通性

    下图是一个典型的双主双从的架构,包括生产者和消费者。

图示

    NameServer 集群中它们相互之间是不通讯。

    >生产者同一时间,与 NameServer 集群中其中一台建立长连接。

    >生产者与 Broker 之间的 Master 保持长连接。

    >消费者同一时间,与 NameServer 集群中其中一台建立长连接。

    >消费者与所有 Broker 建立长连接。

RocketMQ核心组件及整体流程

图示

    RocketMQ 的源码的看起来很多,但是如果按照组件来划分的话,核心只要几个。如下图:

图示

NameServer

    命名服务,更新和路由发现 broker 服务。

    NameServer 要作用是为消息生产者、消息消费者提供关于主题 Topic 的路由信息,NameServer 除了要存储路由的基础信息,还要能够管理Broker 节点,包括路由注册、路由删除等功能。

Producer和Consumer

    java 版本的 mq 客户端实现,包括生产者和消费者。

Broker

    它能接收 producer 和 consumer 的请求,并调用 store 层服务对消息进行处理。HA 服务的基本单元,支持同步双写,异步双写等模式。

Store

    存储层实现,同时包括了索引服务,高可用 HA 服务实现。

Netty Remoting Server 与 Netty Remoting Client

    基于 netty 的底层通信实现,所有服务间的交互都基于此模块。也区分服务端和客户端。

图示

NameServer源码分析

1.RocketMQ核心组件及整体流程

图示

2.NameServer启动流程概要

启动流程

    从源码的启动可知,NameServer单独启动。

    入口类:NamesrvStartup

    核心方法:NamesrvStartup类中main()->main0-> createNamesrvController->start() -> initialize()

图示

步骤一

    解析配置文件,填充 NameServerConfig、NettyServerConfig 属性值,并创建 NamesrvController

    NamesrvStartup类中createNamesrvController 方法

源码示例

这里可以看出,如果你要查看各种参数,直接在启动个参数中送入 -p 就可以打印这个 NameServer 的所有的参数信息

图示
参数信息

    同理,在启动日志中一定可以找到所有的参数:

参数日志文件
具体日志文件内容

步骤二

    根据启动属性创建 NamesrvController 实例,并初始化该实例。NameServerController 实例为 NameServer 核心控制器。

    核心控制器会启动定时任务:

    1、 每隔 10s 扫描一次 Broker,移除不活跃的 Broker

    2、 每隔 10min 打印一次 KV 配置

    NamesrvController 类中 initialize()

源码示例
对应图示

步骤三

    在 JVM 进程关闭之前,先将线程池关闭,及时释放资源

释放资源

最后

start方法调用
方法内容

3.Broker启动流程概要

入口类:BrokerStartup

核心方法:

图示

Broker向NameServer发送消息

start()
registerBrokerAll()
doRegisterBrokerAll()
registerBrokerAll()
registerBroker()
processRequest()
registerBroker()

4.Topic路由注册、剔除机制

(1)路由注册与发现(读写锁,保证消息发送时的高并发)

图示

    消息发送时会获取路由信息,同时 Broker 会定时更新路由信息,所以路由表

    1、 生产者发送消息时需要频繁的获取。对表进行读。

    2、 Broker 定时(30s)会更新一个路由表。对表进行写。为了提高消息发送时的高并发(同时线程安全),这里维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

    写锁

    Broker每隔30s向NameServer报告自己还活着(包含很多信息)这里使用的是写锁。因为数据最终要写入NameServer的内存(使用的 HashMap 进行保存)。

registerBroker()

    读锁

    生产者发送消息时,需要从向 NameServer 获取路由信息,这里使用读锁。

getTopicRouteInfoFromNameServer()
processRequest()

    DefaultRequestProcessor. getRouteInfoByTopic()

getRouteInfoByTopic()

    RouteInfoManager. pickupTopicRouteData()

pickupTopicRouteData()

    设计亮点

    因为Broker每隔30s向NameServer发送一个心跳包,这个操作每次都会更新Broker的状态,但同时生产者发送消息时也需要Broker的状态,要进 行频繁的读取操作。所以这个地方就有一个矛盾,Broker的状态会被经常性的更新,同时也会被更加频繁的读取。这里如何提高并发,尤其是生产者进行消息发送时的并发,所以这里使用了读写锁机制(针对读多写少的场景)。

    NameServer每收到一个心跳包,将更新brokerLiveTable中关于Broker 的状态信息以及路由表( topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable)。更新上述路由表使用了锁粒度较少的读写锁,允许多个消息发送者(Producer)并发读保证消息发送时的高并发。但同一时刻NameServer只处理一个Broker心跳包,多个心跳包请求串行执行。这也是读写锁经典使用场景。

5.路由剔除机制

图示

    Broker 每隔 30s 向NameServer 发送一个心跳包,心跳包包含 BrokerId,Broker 地址,Broker名称,Broker 所属集群名称、Broker 关联的FilterServer列表。但是如果Broker 宕机,NameServer 无法收到心跳包,此时NameServer如何来剔除这些失效的 Broker 呢?NameServer 会每隔10s扫描brokerLiveTable状态表,如果 BrokerLive 的 lastUpdateTimestamp的时间戳距当前时间超过 120s,则认为Broker失效,移除该Broker,关闭与Broker连接,同时更新topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable。

    RocketMQ 有两个触发点来删除路由信息:

    >NameServer 定期扫描 brokerLiveTable 检测上次心跳包与当前系统的时间差,如果时间超过 120s,则需要移除 broker。 

    >Broker 在正常关闭的情况下,会执行 unregisterBroker 指令

    这两种方式路由删除的方法都是一样的,都是从相关路由表中删除与该 broker 相关的信息。

initialize()
scanNotActiveBroker()

6.NameServer的存储

    可知 NameServer 存储以下信息:

图示
读写锁

topicQueueTable:Topic消息队列路由信息,消息发送时根据路由表进行负载均衡

brokerAddrTable:Broker基础信息,包括brokerName、所属集群名称、主备 Broker 地址

clusterAddrTable:Broker集群信息,存储集群中所有 Broker 名称

brokerLiveTable:Broker状态信息,NameServer每次收到心跳包是会替换该信息

filterServerTable:Broker上的 FilterServer列表,用于类模式消息过滤。

NameServer的实现基于内存,NameServer并不会持久化路由信息,持久化的重任是交给 Broker 来完成。

7.客户端启动核心流程

    DefaultMQProducer 是 MQProducer 的唯一默认实现,

    其实现 MQProducer 接口的时候 还继承了 ClientConfig 类 (客户端配置类),可以配置如 sendMsgTimeout 超时时间,producerGroup 生产者组最大消息容量和是否启用压缩等

图示

    RocketMQ 中消息发送者、消息消费者都属于”客户端“

    每一个客户端就是一个 MQClientInstance,每一个 ClientConfig 对应一个示例。

    故不同的生产者、消费端,如果引用同一个客户端配置(ClientConfig),则它们共享一个 MQClientInstance 实例。    

    MQClientInstance 不是对外应用类,也就是说用户不需要自己实例化使用他。并且,MQClientInstance 的实例化并不是直接 new 后使用,而是通过MQClientManager 这个类型。MQClientManager 是个单例类,使用饿汉模式设计保证线程安全。他的作用是提供 MQClientInstance 实例,RocketMQ 认为,MQClientInstance 的实例是可以复用的实例,只要 client 相关特征参数相同,就会复用一个 MQClientInstance 实例,我们可以看看源码。

代码示例
start()
start(true)
getOrCreateMQClientInstance()

    ClientConfig里的相关参数一致,这些 Client 会复用一个 MQClientInstance,使用的时候需要注意,你的程序里的 Client 和 MQClientInstance的对应关 系。下面我们来看看RocketMQ对参数配置如何的 Client 复用一个MQClientInstance,简单来说就是 IP@instanceName@unitName,这么一个串,其中 IP我不解释了,InstanceName可以设置,Producer和Consucer都有相应的 API 设置,如果不设置使用缺省值即 Client的PID,unitName不设置缺省为 null,当然你可以DefaultMQProducer#setUnitName()这个方法设置这个值,这个方法是继承自ClientConfig类的。

源码示例 
代码示例
重复结果
源码示例

8.客户端连接建立的时机

图示

注:客户端(MQClientInstance)中连接的建立时机为按需创建,也就是在需要与对端进行数据交互时才建立的。

代码示例
send()
invokeSync()
getAndCreateChannel()


我是娆疆_蚩梦,让坚持成为一种习惯,感谢各位大佬的:点赞收藏评论,我们下期见!


上一篇:RocketMQ底层原理之高可用机制

下一篇:RocketMQ源码解读之Producer

你可能感兴趣的:(RocketMQ源码解读之NameServer路由机制)