RocketMQ 主从同步读写分离机制

关于主从同步最新理解:RocketMQ 主从同步若干问题答疑

RocketMQ在消息拉取时是如何根据消息消费队列MessageQueue来选择Broker的呢?消息消费队列如图所示:
这里写图片描述
RocketMQ根据MessageQueue查找Broker地址的唯一依据便是brokerName,从RocketMQ的Broker组织实现来看,同一组Broker(M-S)服务器,其brokerName相同,主服务器的brokerId为0,从服务器的brokerId大于0,那RocketMQ根据brokerName如何定位到哪一台Broker上来呢?

PullAPIWrapper#pullKernelImpl

FindBrokerResult findBrokerResult =
            this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                this.recalculatePullFromWhichNode(mq), false);

RocketMQ的MQClientInstance类提供了根据brokerName、brokerId查找Broker地址的方法,返回值如图:
这里写图片描述
MQClientInstance#findBrokerAddressInSubscribe

public FindBrokerResult findBrokerAddressInSubscribe(
        final String brokerName,
        final long brokerId,
        final boolean onlyThisBroker
    ) {
        String brokerAddr = null;
        boolean slave = false;
        boolean found = false;

        HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
        if (map != null && !map.isEmpty()) {
            brokerAddr = map.get(brokerId);
            slave = brokerId != MixAll.MASTER_ID;
            found = brokerAddr != null;

            if (!found && !onlyThisBroker) {
                Entry<Long, String> entry = map.entrySet().iterator().next();
                brokerAddr = entry.getValue();
                slave = entry.getKey() != MixAll.MASTER_ID;
                found = true;
            }
        }

        if (found) {
            return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr));
        }

        return null;
    }
  • brokerName:broker名称;brokerId:brokerId;onlyThisBroker:是否必须返回brokerId的broker对应的服务器信息。
  • brokerAddrTable地址缓存表中根据brokerName获取所有的broker信息。brokerAddrTable的存储格式如:brokerName:{brokerId:brokerAddress}。
  • 根据brokerId从broker主从缓存表中获取指定broker名称,如果根据brokerId未找到相关条目,此时如果onlyThisBroker为false,则随机返回broker中任意一个Broker,否则返回null。
  • 组装FindBrokerResult时,需要设置是否是slave这个属性。如果brokerId=0表示返回的broker是主节点,否则返回的是从节点。

上述方法,根据brokerName是如何获取brokerId的呢?

请看MQClientInstance#recalculatePullFromWhichNode:

public long recalculatePullFromWhichNode(final MessageQueue mq) {
        if (this.isConnectBrokerByUser()) {
            return this.defaultBrokerId;
        }

        AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
        if (suggest != null) {
            return suggest.get();
        }

        return MixAll.MASTER_ID;
    }

首先从pullFromWhichNodeTable缓存表中获取该消息消费队列的brokerId,如果找到,则返回,否则返回brokerName的主节点。由此可以看出pullFromWhichNodeTable中存放的是消息队列建议从从哪个Broker服务器拉取消息的缓存表,其存储结构:MessageQueue:AtomicLong,那该信息从何而来呢?

原来消息消费拉取线程PullMessageService根据PullRequest请求从主服务器拉取消息后会返回下一次建议拉取的brokerId,消息消费者线程在收到消息后,会根据主服务器的建议拉取brokerId来更新pullFromWhichNodeTable,消息消费者线程更新pullFromWhichNodeTable的代码如下:

PullAPIWrapper#processPullResult

this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());
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);
        }
    }

那服务端是如何计算下一次拉取建议从哪台Broker服务器拉取消息呢?

请看:DefaultMessageStore#getMessage

long diff = maxOffsetPy - maxPhyOffsetPulling;
long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE * (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0));
getResult.setSuggestPullingFromSlave(diff > memory);
  • maxOffsetPy:代表当前主服务器消息存储文件最大偏移量,maxPhyOffsetPulling:此次拉取消息最大偏移量。
  • diff:对于PullMessageService线程来说,当前未被拉取到消息消费端的消息长度。
  • TOTAL_PHYSICAL_MEMORY_SIZE:RocketMQ所在服务器总内存大小;accessMessageInMemoryMaxRatio:表示RocketMQ所能使用的最大内存比例,超过该内存,消息将被置换出内存;memory表示RocketMQ消息常驻内存的大小,超过该大小,RocketMQ会将旧的消息置换会磁盘。
  • 如果diff大于memory,表示当前需要拉取的消息已经超出了常驻内存的大小,表示主服务器繁忙,此时才建议从从服务器拉取。

PullMessageProcessor#processRequest

if (getMessageResult.isSuggestPullingFromSlave()) {
     responseHeader.setSuggestWhichBrokerId(subscriptionGroupConfig.getWhichBrokerWhenConsumeSlowly());
} else {
     responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
}

当 GetResult 的 suggestPullingFromSlave 为真是,将会直接返回消息消费组的配置信息whichBrokerWhenConsumeSlowly,默认为1,可以通过客户端命令updateSubGroup配置当主服务器繁忙时,建议从哪个从服务器读取消息。

注意:RocketMQ 读写分离不按套路出牌,并不是主服务器只负责消息发送,消息从服务器主要负责消息拉取,而是只有当主服务器消息拉取出现堆积时才将拉取任务转向从服务器。


备注:本文是《RocketMQ技术内幕》的前期素材,建议关注笔者的书籍:《RocketMQ技术内幕》。


见文如面,我是威哥,热衷于成体系剖析JAVA主流中间件,关注公众号『中间件兴趣圈』,回复专栏可获取成体系专栏导航,回复资料可以获取笔者的学习思维导图
在这里插入图片描述

### RocketMQ 其他消息队列的主从同步实现 #### 1. RocketMQ主从同步机制 RocketMQ 使用 GroupTransferService 来管理主从之间的数据同步过程。具体来说,在主从架构下,Master 节点负责接收生产者发送的消息并将其持久化到磁盘上,随后通过 GroupTransferService 将这些消息复制到 Slave 节点[^2]。Slave 节点会定期向 Master 发送心跳包以确认自身的存活状态,并请求最新的未同步的数据。 当消费者消费消息时,如果启用了读写分离模式,则优先访问 Slave 节点;否则,默认情况下仍然由 Master 提供服务。这种设计可以有效提升系统的可用性吞吐量,同时也降低了单点故障的风险。 然而,在实际部署过程中可能会遇到一些问题,比如由于网络波动或者配置错误等原因造成主从之间无法完成正常的同步操作。此时可以根据日志定位原因,并按照一定流程逐步排查解决问题[^4]。 #### 2. MySQL 的 Binlog 同步方式 MySQL 数据库采用基于二进制日志 (Binary Log, binlog) 的增量复制技术来达成其主从结构下的高一致性保障目标。每当主数据库执行 DML/DQL 类型语句之后都会被记录下来形成一条条事件信息存入文件当中,接着再传输给对应的备机实例解析应用从而达到最终的一致性效果[^1]。 此方法具有延迟低、效率高的特点,但也存在一定的局限性——即对于某些特殊场景可能并不适用(如跨数据中心远距离传播),因此需要结合实际情况灵活调整策略参数设置才能更好地满足业务需求。 #### 3. Elasticsearch 中 Translog 的作用 Elasticsearch 利用事务日志(Transaction Log, translog),作为一种临时存储媒介保存尚未刷入 Lucene 索引内部的新文档变更内容直至条件成熟后再统一提交固化至永久介质之上。与此同时还会周期性的把最新版本的内容推送给副本节点确保整体集群健康稳定运行不受影响。 这种方式不仅能够快速响应用户的查询请求而且还能很好地支持大规模布式环境下的高效协作工作流模型构建起来更加容易维护升级成本更低廉可靠程度更高。 #### 4. Redis AOF 功能简介及其优势析 Redis 支持两种主要形式之一就是追加仅允许(Append Only File,AOF),它会在每次命令处理完成后都将该次修改动作完整地追记成一行文本字符串附加到指定位置处的一个普通纯文本格式文件里去以便后续恢复重建整个内存映像图谱之需所用。 相比起 RDB 方式而言虽然速度稍慢一点但是胜在其精确度极高几乎不会丢失任何已发生的改动历史轨迹所以特别适合那些对数据安全性要求极高的应用场景场合选用作为首选解决方案考虑对象之一。 ```python def check_sync_status(master_ip, slave_ips): """ 检查主从同步的状态 :param master_ip: 主节点IP地址 :param slave_ips: 多个从节点IP列表 :return: 返回各节点同步情况报告字典 """ sync_report = {} try: for ip in slave_ips: response = request_data_from_node(ip) if 'last_update_time' not in response or \ abs(response['last_update_time'] - get_master_last_update(master_ip)) > SYNC_THRESHOLD: status = "Out of Sync" else: status = "In Sync" sync_report[ip] = {"status": status} except Exception as e: print(f"Error occurred while checking {ip}: {e}") return sync_report ``` 上述函数用于检测一组从属节点相对于某个特定主机的时间戳差异是否超出预定义阈值范围之外进而判断它们当前是否存在不同步现象发生状况。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

中间件兴趣圈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值