持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
RocketMQ高可用
RocketMQ
高可用分为在各种组件本身,副本,组件之间的负载均衡
RocketMQ组件有NameServer
、Broker
、Producer
、Consumer
,下面是对每个组件实现高可用的解析
NameServer组件
简述
NameServer
是命名服务、注册中心,主要包括两个功能:Broker
管理,NameServer
接受Broker
集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker
是否还存活;路由信息管理,每个NameServer
将保存关于Broker
集群的整个路由信息和用于客户端查询的队列信息。然后Producer
和Conumser
通过NameServer
就可以知道整个Broker
集群的路由信息,从而进行消息的投递和消费。
Broker与NameServer通信
NameServer
可部署为集群模式,节点间无依赖。当用于Broker
注册时,Broker
会注册到每一个NameServer
上面,这样注册可保证每个NameServer
上面都有信息,即使某一个挂掉,其他依然能提供服务。
Broker
循环注册源码如下:
for (String namesrvAddr : nameServerAddressList) {
try {
RegisterBrokerResult result = this.registerBroker(namesrvAddr, clusterName, brokerAddr, brokerName, brokerId,
haServerAddr, topicConfigWrapper, filterServerList, oneway, timeoutMills);
if (result != null) {
registerBrokerResult = result;
}
log.info("register broker to name server {} OK", namesrvAddr);
} catch (Exception e) {
log.warn("registerBroker Exception, {}", namesrvAddr, e);
}
}
Producer、Consumer与NameServer通信
Producer
、Consumer
与NameServer
通信是选择一个进行通信,实现代码是NettyRemotingClient
中的this.namesrvAddrChoosed.get()
,会返回一个可连接的NameServer
,如果没有找到,会从列表中寻找一个可用的选择。源码如下:
if (addrList != null && !addrList.isEmpty()) {
for (int i = 0; i < addrList.size(); i++) {
int index = this.namesrvIndex.incrementAndGet();
index = Math.abs(index);
index = index % addrList.size();
String newAddr = addrList.get(index);
this.namesrvAddrChoosed.set(newAddr);
Channel channelNew = this.createChannel(newAddr);
if (channelNew != null)
return channelNew;
}
}
Broker组件
Broker
通过集群模式来保证高可用,实现多主从,主节点可读写,从节点只读。二者之间通信使用CommitLog
这个关键记录实体,主节点会发送新的CommitLog
给从节点,从节点定时将CommitLog
的记录位置传回给主节点。
Broker
角色分为ASYNC_MASTER
(异步主机)、SYNC_MASTER
(同步主机)以及SLAVE
(从机)。如果对消息的可靠性要求比较严格,可以采用SYNC_MASTER
加SLAVE
的部署方式。如果对消息可靠性要求不高,可以采用ASYNC_MASTER
加SLAVE
的部署方式。如果只是测试方便,则可以选择仅ASYNC_MASTER
或仅SYNC_MASTER
的部署方式。
主节点通知从节点进度的方法是HAConnection.this.haService.notifyTransferSome(HAConnection.this.slaveAckOffset);
写数据时,选择CommitLog
的方法是SelectMappedBufferResult selectResult = HAConnection.this.haService.getDefaultMessageStore().getCommitLogData(this.nextTransferFromWhere);
,SelectMappedBufferResult
就是映射的CommitLog
;
SYNC_MASTER
(同步主机)实现是在写入线程后面,先判断消息是否可存储,可以的话,判断传递消息大小是否符合从节点要求(写入数据+写入位置),符合要求后会获取request,没有新建; 拿到request后发送,发送成功后唤醒写入的线程,等待消息发送结束,从节点收到消息后会向主节点发送进度消息,主节点唤醒boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
。从节点不可用则返回从节点问题PutMessageStatus.SLAVE_NOT_AVAILABLE
。
Producer组件
Producer
发送消息会先根据Topic
找到指定的TopicPublishInfo
,在获取了TopicPublishInfo
路由信息后,RocketMQ
的客户端在默认方式下selectOneMessageQueue()
方法会从TopicPublishInfo
中的messageQueueList
中选择一个队列(MessageQueue)进行发送消息。
MessageQueue tmpmq = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
if (tmpmq != null) {
mq = tmpmq;
brokersSent[times] = mq.getBrokerName();
try {
// 省略...
sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);
this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
}
// 省略...
}
发送核心机制是latencyFaultTolerance
,这个意思是每次发送如果失败会等待一定超时时间再次重试,重试失败再次重试,这个重试时间间隔会相应增加。
Consumer组件
Consumer
有两种消费模式(Push/Pull)都是基于拉模式来获取消息的,而在Push模式只是对pull模式的一种封装,其本质实现为消息拉取线程在从Broker
批量拉取到消息后,然后提交到消息消费线程池后,又继续向服务器再次尝试拉取消息。如果未拉取到消息,则延迟一下又继续拉取。
Consumer
实例的启动流程中的启动MQClientInstance
实例部分,会完成负载均衡服务线程RebalanceService
的启动(每隔20s执行一次)。Consumer
从所有Broker
拉取消息。
小结
以上概述了NameServer
、Broker
、Producer
、Consumer
四种组件实现高可用的方式,并用源码直观展示了实现内容,更能理解平时使用的一些原理。