重新认识 RocketMQ (3)- 高可用

103 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

RocketMQ高可用

RocketMQ高可用分为在各种组件本身,副本,组件之间的负载均衡

RocketMQ组件有NameServerBrokerProducerConsumer,下面是对每个组件实现高可用的解析

NameServer组件

简述

NameServer是命名服务、注册中心,主要包括两个功能:Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后ProducerConumser通过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通信

ProducerConsumerNameServer通信是选择一个进行通信,实现代码是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_MASTERSLAVE的部署方式。如果对消息可靠性要求不高,可以采用ASYNC_MASTERSLAVE的部署方式。如果只是测试方便,则可以选择仅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拉取消息。

小结

以上概述了NameServerBrokerProducerConsumer四种组件实现高可用的方式,并用源码直观展示了实现内容,更能理解平时使用的一些原理。