「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」
NameServer用来做服务注册与发现,防止服务器的单点故障导致整个系统瘫痪,不可用,集群环境下的系统都需要一个服务注册中心,现在微服务中用的比较多的是zk,也是比较成熟,但是RoctetMQ自己写了一套,主要是因为NameServer不考虑集群操作,只要实现:动态路由的发现与剔除,能够拉取路由信息即可。NameServer每台机器之间互不通信,在Broker启动的时候需要像每台NameServer注册自己的信息,逻辑部署图如下:
这样的哈,每台Broker启动的时候会向每台NameServer注册自己的信息,消息生产者在发送消息的时候会先从NameServer获取Broker服务的地址列表,然后生产者根据负载均衡算法从列表中选择一条消息服务器进行消息发送。生产者也是一样从NameServer拉取Broker服务的地址列表,获取Broker的地址,然后采用负载算法,从Broker中拉取消息。从初步来看,NameServer具备下面的几个功能:
- 维护Broker的路由元数据信息
- Broker服务路由注册功能
- 当Broker宕机后,NameServer需要发现,自动剔除功能
- 心跳机制,需要知道Broker是否存活
- 生产者、消费者拉取路由信息
下面就从源码一步步分析NameServer是如何实现这些功能?
启动主流程
org.apache.rocketmq.namesrv.NamesrvStartup#main
在main方法中,只有两行核心代码,创建了一个NamesrvController,然后启动
NamesrvController controller = createNamesrvController(args);
start(controller);
复制代码
创建NamesrvController
org.apache.rocketmq.namesrv.NamesrvStartup#createNamesrvController
在创建NamesrvController主要干了两件事:
- 根据设定的配置文件路径地址,解析出参数配置,然后填充:NamesrvConfig、NettyServerConfig属性值
通过debug可以明确的看到NamesrvConfig、NettyServerConfig都有哪些属性
- NamesrvConfig:基础配置信息
- NettyServerConfig:Netty服务的配置信息
- 设置日志的上下文后,创建NamesrvController返回
启动服务
org.apache.rocketmq.namesrv.NamesrvStartup#start
在启动服务的时候,调用了NamesrvController的三个方法
- controller.initialize() 初始化服务启动的相关信息
- 注册JVM钩子函数,关闭服务:controller.shutdown()
- controller.start():启动服务
controller.initialize()
org.apache.rocketmq.namesrv.NamesrvController#initialize
在初始化中,做了下面的几件事
- 从配置文件中加载出KV配置
- 初始化Netty服务
- 设置处理消息的线程池
- 设置请求处理器
- 开启定时任务1:每隔10s扫描一次不存在的Broker,在这里实现了路由消息的定时剔除功能,在后面详细分析
- 开启定时任务2:每隔10s打印KV的配置
- 设置Tls连接,证书信息
controller.start()
org.apache.rocketmq.namesrv.NamesrvController#start
启动Netty服务,提供对外服务
controller.shutdown();
org.apache.rocketmq.namesrv.NamesrvController#shutdown
关闭netty服务及线程池关闭
netty的主流程主要就干了两件事:
- 启动Netty服务,用于对外提供服务
- 启动线程池,每隔10s扫描不存活的Broker
接下来从功能点来看下是如何实现那些功能的?
路由信息
org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager
在RouteInfoManager类中,维护5个HashMap,这5个HashMap一起组成了NameServer的元信息
topicQueueTable
HashMap<String/* topic */, List> topicQueueTable
存放每个Topic对应MessageQueue队列的信息,一个Topic对应多个消息队列
brokerAddrTable
HashMap<String/* brokerName */, BrokerData> brokerAddrTable
broker的基础信息 在BrokerData中维护了Broker的主从地址, HashMap<Long/* brokerId /, String/ broker address */> brokerAddrs; brokerId=0,代表是主Broker地址,大于0则为从Broker的地址
clusterAddrTable
HashMap<String/* clusterName /, Set<String/ brokerName */>> clusterAddrTable
Broker集群信息,存储集群中所有的Broker名称,一个集群对应多个Broker
brokerLiveTable
HashMap_<String/* brokerAddr */, BrokerLiveInfo> _brokerLiveTable
Broker的存活信息,每次收到Broker的心跳信息,都会更新里面的lastUpdateTimestamp时间戳
filterServerTable
HashMap_<String/* brokerAddr */, List<String>/* Filter Server */> _filterServerTable
Broker中的 FilterServer列表,用于消息的过滤
Broker注册
org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#processRequest
nettty收到请求的消息后,会将数据解析后交给DefaultRequestProcessor#processRequest来处理,根据消息体中的code来区分该消息的用途
注册Broker消息调用的是: org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#registerBrokerWithFilterServer 方法
在Broker注册的过程中,主要干了下面几点:
- 对Request请求的消息进行数据读取封装到:RegisterBrokerRequestHeader中后,交给RouteInfoManager#registerBroker 来跟新元信息
在Broker注册的时候,会将自己的Broker信息都带过来
- RouteInfoManager#registerBroker 更新Broker注册信息
- 维护clusterAddrTable元数据
先根据集群名,取出所有的broker集合,如果broker名集合为空,则表示改集群broker不存在,需要构建一个空的集合
- 维护brokerAddrTable元数据
根据Broker名称取出Boker的数据信息(BrokerData),如果不存在则构建一个新的对象,存在则找到当前Broker下的所有IP地址,当前的Broker的主从关系不一致,就从该连接从元信息中剔除后在插入
这步是客户端的主从关系发生变化,则元数据也需要变更主从关系
- 维护topicQueueTable 元数据
org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#createAndUpdateQueueData
当为主Broker的时候,并且Broker的Topic的配置发生变化后,才为更新topicQueueTable 数据
- 维护brokerLiveTable元数据,更新心跳时间戳
- 更新注册完成后,构建响应信息给客户端
Broker心跳
Broker的心跳机制就是调用的事Broker注册流程,相当于每隔一段时间都发送一次注册信息,在每次执行注册的时候都更新时间戳
Broker不存活剔除
剔除不存活的Broker,NameServer采用的是定时任务,每隔10秒调用一次方法:
org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#scanNotActiveBroker
剔除的实现逻辑比较简单,将维护的每个BrokerLiveInfo都拿出来遍历一次,判断最近更新时间:lastUpdateTimestamp的是不是两分钟前的,如果是则剔除(需要删除每个元数据信息),然后关闭连接通道
路由信息拉取
RocketMQ的元数据信息发生变化后,NameServer不会主动推送给客户端,而是客户端自己主动来拉取最新的路由,通过:RequestCode.GET_ROUTEINFO_BY_TOPIC来确定操作方法
org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#getRouteInfoByTopic
根据传入的TopicName获取TopicRouteData信息后响应给客户端
根据上面的源码,可以总结出大概下面的一张图,表示NameServer的主要干的工作