文章目录
上篇文章里提到,NameServer在Mq架构中充当一个路由中心的角色,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能
- Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
- 路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费
RouteInfoManager
NameServer对Broker信息的管理,对路由信息的管理,都在RouteInfoManager类中实现。
先看下RouteInfoManager中的存储的路由数据及结构
// Broker信息过期失效时间(120秒),若 上次心跳时间+BROKER_CHANNEL_EXPIRED_TIME>当前时间,则判断Broker失效
private final static long BROKER_CHANNEL_EXPIRED_TIME = 1000 * 60 * 2;
// 读写锁,控制下列各个Map在并发读写下的安全性
private final ReadWriteLock lock = new ReentrantReadWriteLock();
// topic对应队列信息
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
// broker信息
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
// broker集群信息,key-集群名称 value-对应集群中所有broker名称
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
// broker地址及对应对应broker存活信息
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
// broker地址及对应Filter Server列表
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
再看下QueueData、BrokerData、BrokerLiveInfo中属性
QueueData:
//brokerName
private String brokerName;
//读队列数量
private int readQueueNums;
//写队列数量
private int writeQueueNums;
//权限值 参考PermName类
private int perm;
//还不知道具体有啥用
private int topicSynFlag;
一个topic有多个消息队列,这些消息队列分布在不同的broker上,一个Queuedata对应一个broker上某topic对应读写队列的结构
BrokerData
// broker集群名称
private String cluster;
// broker名称
private String brokerName;
// 主备broker地址,主brokerId为0
private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs;
BrokerLiveInfo
// 上次broker心跳检查时的时间
private long lastUpdateTimestamp;
// topicConfig的版本号
private DataVersion dataVersion;
// Broker与NameServer链接通道
private Channel channel;
// ha地址
private String haServerAddr;
broker在启动时,向NameServer进行注册,启动后也会定时(每隔30秒)定时向NameServer进行注册以维持心跳检测,每次注册,broker的信息都会存储到BrokerLiveInfo中。
假设broker宕机了,那么BrokerLiveInfo中对应lastUpdateTimestamp就不再更新,Nameserver每隔120秒扫描brokerLiveTable中broker存活信息,判断 lastUpdateTimestamp + BROKER_CHANNEL_EXPIRED_TIME(120秒) > 当前时间,即broker在120秒内都未进行心跳注册时,移除broker信息
路由注册(Broker注册)
Broker发起注册请求
介绍完了,RouteInfoManager中的存储数据结构,下面从Broker路由信息注册开始分析源码
Broker启动时,像NameServer进行注册,代码在org.apache.rocketmq.broker.BrokerController#start中,可以看到定时(每隔30秒)向nameServer发起注册请求
进到registerBrokerAll方法查看具体broker注册逻辑,
总得来说就是两步
- 组织topicConfig信息,判断broker读写权限,若不可读,或不可写,更新topic对应权限信息
- 判断是否需要进行注册,由于forceRegister默认为true,所以必然会进行broker信息注册
最后进行路由注册的请求方法在org.apache.rocketmq.broker.out.BrokerOuterAPI#registerBrokerAll中,主要就是封装了请求参数,最后调用NettyClient发送请求进行broker注册,具体Netty请求方法在org.apache.rocketmq.broker.out.BrokerOuterAPI#registerBroker,请求RequestCode为REGISTER_BROKER
NameServer接收注册请求
上篇文章提到,NameServer模块中默认的Netty事件处理器为DefaultRequestProcessor,查询对应RequestCode为REGISTER_BROKER即可找到接收注册的方法,最后调用的是
RouteInfoManager#registerBroker方法,方法比较长,逐步分析
@1:因为每台Broker都会向所有NameServer进行注册,所以存在多台Broker同时向一个NameServer注册的情况,加写锁保证数据安全
@2:维护HashMap<String/* clusterName /, Set<String/ brokerName */>> clusterAddrTable,判断Broker集群中是否有该Broker,没有则加入
@3,@4:维护HashMap<String/* brokerName */, BrokerData> brokerAddrTable,主要是对Broker地址的维护,具体内容看注释
@5:维护HashMap<String/* topic */, List> topicQueueTable,具体维护逻辑在createAndUpdateQueueData中,基本逻辑就是对broker中每个默认topic注册其QueueData信息
@6: 每次Broker注册时,将最新Broker信息存入HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable中,其中lastUpdateTimestamp是NameServer后续判断Broker是否失效的关键参数
@7:如果当前Broker是从节点,则查找BrokerId=0的主节点,并将HaServer地址及Master地址设为主节点地址
路由查询(QueueData、BrokerData信息查询)
Producer在消息发送时,会先从NameServer查询topic对应路由信息,对应代码实现在org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl中
查询路由信息的Netty请求封装在org.apache.rocketmq.client.impl.MQClientAPIImpl#getTopicRouteInfoFromNameServer中
RequestCode为GET_ROUTEINTO_BY_TOPIC
NameServer端对路由信息查询的请求处理方法为RouteInfoManager#getRouteInfoByTopic,根据topic信息查询了QueueData列表,及BrokerData列表,封装成TopicRouteData返回
路由信息删除
如果Broker宕机,那么NameServer里的该Broker相关路由信息需要被移除,NameServer启动时开启一个定时器,每隔10秒scanNotActiveBroker,遍历brokerLiveTable,判断:若当前时间>上次心跳时间+120秒,则关闭通道,清除broker信息,最终调用RouteInfoManager#onChannelDestroy方法清除路由元信息
其维护代码与新增路由信息是个相反过程,不再赘述
若Broker与NameServer之间的长连接正常断开,因为Nameserver中的NettyServer启动时绑定了BrokerHousekeepingService,而BrokerHousekeepingService又是继承了ChannelEventListener,所以长连接关闭或者异常时,会触发BrokerHousekeepingService中对应Channel事件,最终也会调用RouteInfoManager#onChannelDestroy清除路由信息
看完整篇文章,其核心在于Broker路由注册及路由查询,但有一个关键点,Broker失效后,NameServer是定时查询失效Broker并删除路由信息的,也就是说,在某一个时刻,Broker已经宕机了,但是NameServer中还维持有该Broker路由信息,这样Producer在查询路由信息时,就可能从NameServer查询到一个已经失效的路由信息,这不是违背了高可用的原则了?
实际上Mq在消息发送时,针对这种情况做了容错处理,具体分析在下篇Mq消息发送时做具体介绍