一个得不到执行的念头只会消亡。
——罗杰.冯.欧克
大纲
源码分析的内容比较多,当然我们不可能把 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 进程关闭之前,先将线程池关闭,及时释放资源
最后
3.Broker启动流程概要
入口类:BrokerStartup
核心方法:
Broker向NameServer发送消息
4.Topic路由注册、剔除机制
(1)路由注册与发现(读写锁,保证消息发送时的高并发)
消息发送时会获取路由信息,同时 Broker 会定时更新路由信息,所以路由表
1、 生产者发送消息时需要频繁的获取。对表进行读。
2、 Broker 定时(30s)会更新一个路由表。对表进行写。为了提高消息发送时的高并发(同时线程安全),这里维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
写锁
Broker每隔30s向NameServer报告自己还活着(包含很多信息)这里使用的是写锁。因为数据最终要写入NameServer的内存(使用的 HashMap 进行保存)。
读锁
生产者发送消息时,需要从向 NameServer 获取路由信息,这里使用读锁。
DefaultRequestProcessor. getRouteInfoByTopic()
RouteInfoManager. 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 相关的信息。
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 实例,我们可以看看源码。
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)中连接的建立时机为按需创建,也就是在需要与对端进行数据交互时才建立的。
我是娆疆_蚩梦,让坚持成为一种习惯,感谢各位大佬的:点赞、收藏和评论,我们下期见!
上一篇:RocketMQ底层原理之高可用机制
下一篇:RocketMQ源码解读之Producer