RocketMQ:读写分离机制

一、前言

        一般来说,选择主从备份实现高可用的架构中,都会具备读写分离机制,比如 MySQL 读写分离,客户端可以向主从服务器读取数据,但客户写数据只能通过主服务器。

        那么,RocketMQ Slave服务器如何参与到消息拉取负载均衡机制?

二、原理

        RocketMQ 的读写分离机制又跟上述描写的不太一致,它有属于自己的一套读写分离逻辑,会判断主服务器的消息堆积量来决定消费者是否向从服务器拉取消息消费。

        首先,消息消费者在向 Broker 发送消息拉取请求时,会根据筛选出来的消息队列,判定是从Master,还是从Slave拉取消息,默认是Master。

        Broker 接收到消息消费者拉取请求,在获取本地堆积的消息量后,会计算服务器的消息堆积量是否大于物理内存的一定值,如果是,则标记下次从 Slave服务器拉取,计算 Slave服务器的 Broker Id,并响应给消费者。

        消费者在接收到 Broker的响应后,会把消息队列与建议下一次拉取节点的 Broker Id 关联起来,并缓存在内存中,以便下次拉取消息时,确定从哪个节点发送请求。

三、源码分析

        决定消费者是否向从服务器拉取消息消费的值存在 GetMessageResult 类中:

org.apache.rocketmq.store.GetMessageResult代码片段
public class GetMessageResult {
	private boolean suggestPullingFromSlave = false;
}

        其默认值为 false,即默认消费者不会消费从服务器,但它会在消费者发送消息拉取请求时,动态改变该值,Broker 接收、处理消费者拉取消息请求:

org.apache.rocketmq.store.DefaultMessageStore#getMessage代码片段
long diff = maxOffsetPy - maxPhyOffsetPulling;
long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE
    * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0));
getResult.setSuggestPullingFromSlave(diff > memory); //算出当前消息堆积量是否大于物理内存的 40%,设置是否向从服务器拉取消息

        其中 maxOffsetPy 为当前最大物理偏移量,maxPhyOffsetPulling 为本次消息拉取最大物理偏移量,他们的差即可表示消息堆积量,TOTAL_PHYSICAL_MEMORY_SIZE 表示当前系统物理内存,accessMessageInMemoryMaxRatio 的默认值为 40,以上逻辑即可算出当前消息堆积量是否大于物理内存的 40 %,如果大于则将 suggestPullingFromSlave 设置为 true。

        接下来该参数值会在消息拉取逻辑里面产生作用:

org.apache.rocketmq.broker.processor.PullMessageProcessor代码片段
private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend)
        throws RemotingCommandException {

	//省略部分代码...
	
	//处理客户端拉取消息请求,获取存储消息
    final GetMessageResult getMessageResult =
        this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(),
            requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter);
    if (getMessageResult != null) {
        response.setRemark(getMessageResult.getStatus().name());
        responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset());
        responseHeader.setMinOffset(getMessageResult.getMinOffset());
        responseHeader.setMaxOffset(getMessageResult.getMaxOffset());
		//是否建议向从服务器拉取消息
        if (getMessageResult.isSuggestPullingFromSlave()) { 
            responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
        } else {
            responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
        }
		switch (this.brokerController.getMessageStoreConfig().getBrokerRole()) {
            case ASYNC_MASTER:
            case SYNC_MASTER:
                break;
            case SLAVE:
                if (!this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
                    response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                    responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
                }
                break;
        }

        if (this.brokerController.getBrokerConfig().isSlaveReadEnable()) {
            // consume too slow ,redirect to another machine
            if (getMessageResult.isSuggestPullingFromSlave()) {
                responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
            }
            // consume ok
            else {
                responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getBrokerId()); //设置为从服务器 broker ID
            }
        } else {
            responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
        }
	
		//省略部分代码...
	}
	//省略部分代码...
	return response;
}

        如果发现主服务器的消息堆积超过了物理内存的 40%,则会设置 suggestWhichBrokerId 为从服务器 broker ID。

        这里还会有个 slaveReadEnable 值来决定是否可以从从服务器拉取消息:

  • 如果 slaveReadEnable=true,并且堆积量已经超过物理内存 40%时,则建议从从服务器拉取消息,否则还是从主服务器拉取消息;
  • 如果 slaveReadEnable=false,则消息者只能从主服务器中拉取消息。

        当消费者收到拉取响应回来的数据后,会将下次建议拉取的 brokerID 缓存起来。

org.apache.rocketmq.client.impl.consumer.PullAPIWrapper#updatePullFromWhichNode代码片段
public void updatePullFromWhichNode(final MessageQueue mq, final long brokerId) {
    AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
    if (null == suggest) {
        this.pullFromWhichNodeTable.put(mq, new AtomicLong(brokerId));
    } else {
        suggest.set(brokerId);
    }
}

        下次拉取消息就会从 pullFromWhichNodeTable 中取出拉取 brokerId。

org.apache.rocketmq.client.impl.consumer.PullAPIWrapper#recalculatePullFromWhichNode代码片段
public long recalculatePullFromWhichNode(final MessageQueue mq) {
    if (this.isConnectBrokerByUser()) {
        return this.defaultBrokerId;
    }
	//获取推荐的brokerId
    AtomicLong suggest = this.pullFromWhichNodeTable.get(mq); 
    if (suggest != null) {
        return suggest.get();
    }
    return MixAll.MASTER_ID;
}

引用

《RocketMQ技术内幕》
https://mp.weixin.qq.com/s/duweizStvKkCbrY03g2zkg

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值