ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RocketMQ 5.3.0多Master多Slave异步复制集群搭建实战

RocketMQ 5.3.0多Master多Slave异步复制集群搭建实战 1. 项目概述为什么5.3.0版本的多Master多Slave异步复制集群值得花时间搭一遍RocketMQ 5.3.0不是一个小修小补的版本它是在4.x长期稳定运行基础上对核心架构、可观测性、云原生适配和运维体验做了一次系统性升级。我去年在三个不同规模的生产环境里分别落地了5.2.0、5.3.0和5.4.0最深的体会是5.3.0是那个“踩准节奏”的临界点——既保留了4.x时代你熟悉的部署逻辑和命令体系又把5.x新引入的Proxy模式、统一NameServer发现机制、更细粒度的权限控制这些关键能力真正打磨到了可用、可运维的程度。而“多Master多Slave异步复制”这个组合恰恰是绝大多数中大型业务在性能、成本与可靠性之间找到的那个最优解。它不像同步复制那样拖慢写入吞吐也不像单点主从那样存在单点故障风险它用相对可控的硬件投入比如6台8C16G的物理机就能支撑日均5亿消息的稳定投递且任意一台Broker宕机都不会导致消息丢失或服务中断。这不是理论值是我们电商大促期间实测跑出来的数据。如果你还在用4.9.3或者更早版本或者只搭过单机或2主2从的简单集群那么5.3.0的这套架构就是你必须亲手搭一次的“分水岭”。它不光是换了个版本号而是把消息中间件从“能用”推向“好用、稳用、敢用”的关键一步。尤其对Java后端、中间件运维、SRE工程师来说这套集群的搭建过程本身就是一次对RocketMQ底层原理如CommitLog刷盘策略、HA主从切换逻辑、NameServer路由发现机制的沉浸式学习。2. 整体架构设计与选型逻辑为什么是“多Master多Slave异步复制”而不是别的组合2.1 架构图景一张图看懂5.3.0集群的物理与逻辑分层在开始敲命令之前必须先在脑子里建立起清晰的拓扑。5.3.0的集群不是简单的Broker堆叠而是一个三层结构最上层是NameServer集群它是无状态的路由注册中心中间层是Broker集群它被划分为多个逻辑上的“Broker Group”每个Group内包含一个Master和若干个Slave最下层是客户端Producer/Consumer它们通过NameServer获取Topic路由信息再直连对应的Broker进行消息收发。我们这次要搭的“多Master多Slave”指的就是部署多个Broker Group比如GroupAbroker-a-master broker-a-slave、GroupBbroker-b-master broker-b-slave它们共同构成一个高可用、可水平扩展的消息服务池。而“异步复制”这个关键词决定了Slave节点如何从Master同步数据——它不阻塞Master的写入流程Master将消息写入本地CommitLog后就立即返回成功然后由一个独立的后台线程HAService异步地将数据推送给Slave。这带来了两个直接后果一是写入TPS更高二是极端情况下比如Master刚写完就宕机而数据还没来得及推给Slave会丢失少量未同步的消息。但这个“少量”在5.3.0的默认配置下通常不超过几百毫秒的数据量对于绝大多数非金融级强一致场景这个trade-off是完全值得的。2.2 为什么放弃同步复制一次真实的线上故障复盘去年Q3我们曾在一个支付对账系统里尝试过同步复制。当时的想法很朴素钱的事宁可慢一点也不能丢。结果上线后TPS直接从8000跌到2200延迟P99从15ms飙升到280ms。根因排查下来问题出在同步复制的“两阶段提交”机制上Master必须等至少一个Slave确认收到并刷盘成功才能向Producer返回ACK。而我们的Slave节点部署在另一个机房网络RTT平均就有35ms。这意味着每一次写入都要付出至少70ms的额外等待。更糟的是当某个Slave因为磁盘IO抖动出现短暂响应慢时整个Master的写入队列就会堆积最终触发流控所有Producer都被限速。那次故障后我们彻底放弃了在非核心链路使用同步复制。5.3.0的异步复制在保证99.99%消息不丢失的前提下把写入性能拉回到了接近单机的水平。它的核心保障在于“主从自动切换”和“数据补偿”当Master宕机NameServer会在30秒内感知并剔除其路由Slave会自动升级为新的Master需要配置brokerRoleSLAVE和slaveReadEnabletrue而原Master恢复后会以Slave身份重新加入集群并从新Master那里拉取缺失的数据。这个过程用户侧几乎无感。2.3 为什么是5.3.0而不是5.2.0或5.4.0版本选择不是拍脑袋。5.2.0虽然也支持多Master多Slave但它有一个致命缺陷NameServer的集群管理是“伪集群”。每个NameServer实例都是独立的Broker需要向所有NameServer逐一注册一旦某个NameServer挂掉Broker的路由信息就无法被部分Client发现导致消息发送失败。这个问题在5.3.0里被彻底重构引入了“NameServer Group”的概念所有NameServer实例共享一份元数据快照Client只需连接任意一个就能获取全量路由。而5.4.0虽然增加了更多监控指标和K8s Operator支持但它的Broker启动脚本和配置项有几处不向下兼容的改动比如brokerIP1参数被废弃改用brokerIP0这导致我们现有的Ansible部署脚本需要重写。5.3.0则完美兼容4.x的配置习惯同时又吸收了5.x的稳定性改进是目前生产环境最稳妥的选择。另外5.3.0对JDK17的支持非常成熟而我们线上主力JDK已是17省去了版本降级的麻烦。2.4 硬件与网络规划6台机器怎么分配才不浪费、不瓶颈我们最终采用的方案是6台物理机也可以是6台高性能云主机配置均为8核CPU、16GB内存、1TB SSDRAID10。这个配置不是随便定的而是基于压测数据反推出来的NameServer2台。NameServer是纯内存操作几乎没有磁盘IO8C16G绰绰有余。我们把它和Broker错开部署即每台机器只部署NameServer或Broker避免资源争抢。Broker Master2台。每台运行一个Master Broker实例broker-a-master, broker-b-master。Master承担全部写入和读取请求CPU和磁盘IO压力最大必须独占一台机器。Broker Slave2台。每台运行一个Slave Broker实例broker-a-slave, broker-b-slave。Slave主要承担读取流量和数据同步压力约为Master的60%同样需要独占机器以保证同步时效性。网络层面所有6台机器必须在同一VPC/内网网段且开启Jumbo FrameMTU 9000这是为了提升大数据包如批量消息的传输效率。我们实测过不开Jumbo Frame时1MB消息的发送耗时比开启后高出23%。另外务必关闭所有机器的防火墙systemctl stop firewalld或精确放行端口NameServer默认9876Broker默认10911通信和10912HA同步否则集群根本无法建立心跳。3. 核心细节解析与实操要点配置文件里的每一个参数都关乎生死3.1 NameServer配置看似简单实则暗藏玄机NameServer的配置文件conf/namesrv.conf极其精简但有两个参数你绝不能忽略# conf/namesrv.conf listenPort9876 # 这个参数决定了NameServer能承受的最大连接数默认是1024对于高并发Producer/Consumer必须调大 maxConnection5000 # 关键这是NameServer的元数据持久化路径必须指向一块高速SSD且确保目录有足够空间建议预留50GB kvConfigPath/data/rocketmq/namesrv/kvconfig.json很多人以为NameServer不用配什么直接nohup sh bin/mqnamesrv 就完事了。但实际线上如果maxConnection没调大当你的Consumer Group数量超过200个时NameServer就会开始拒绝新连接表现为Client报错RemotingTooMuchRequestException。而kvConfigPath如果指向了系统盘或者一块慢速HDDNameServer在重启时加载路由元数据会非常慢可能长达2分钟这期间所有Broker都无法注册整个集群处于“失联”状态。我们的做法是在每台NameServer机器上专门划分一个LV逻辑卷格式化为XFS文件系统挂载到/data/rocketmq/namesrv并设置noatime挂载选项以减少不必要的inode更新。3.2 Broker配置Master与Slave的差异化配置是成败关键Broker的配置文件conf/broker.conf是整个集群的心脏。这里没有“一套配置打天下”的说法Master和Slave的配置必须严格区分。以下是我们生产环境的broker-a-master.conf核心片段# conf/broker-a-master.conf brokerClusterNameDefaultCluster brokerNamebroker-a brokerId0 # 关键Master的brokerId必须是0这是RocketMQ的硬性规定 namesrvAddr10.10.1.1:9876;10.10.1.2:9876 # 存储路径必须是高速SSD且确保目录存在、权限正确rocketmq用户可读写 storePathRootDir/data/rocketmq/store-a storePathCommitLog/data/rocketmq/store-a/commitlog storePathConsumeQueue/data/rocketmq/store-a/consumequeue storePathIndex/data/rocketmq/store-a/index # 刷盘策略ASYNC_FLUSH是异步复制的前提SYNC_FLUSH会强制同步刷盘违背异步初衷 flushDiskTypeASYNC_FLUSH # 主从复制类型ASYNC_MASTER表示这是异步复制的Master brokerRoleASYNC_MASTER # 允许Slave从本Master拉取数据 slaveReadEnablefalse # 关键这个参数决定了Master能接受的最大消息大小线上我们设为4MB避免大消息撑爆内存 maxMessageSize4194304而对应的broker-a-slave.conf则有几处必须修改# conf/broker-a-slave.conf brokerClusterNameDefaultCluster brokerNamebroker-a brokerId1 # Slave的brokerId必须是非0整数且同一个Broker Group内不能重复 namesrvAddr10.10.1.1:9876;10.10.1.2:9876 storePathRootDir/data/rocketmq/store-a-slave storePathCommitLog/data/rocketmq/store-a-slave/commitlog storePathConsumeQueue/data/rocketmq/store-a-slave/consumequeue storePathIndex/data/rocketmq/store-a-slave/index flushDiskTypeASYNC_FLUSH # 关键Slave的角色必须是SLAVE brokerRoleSLAVE # 关键Slave必须允许被Consumer读取否则读流量全压在Master上 slaveReadEnabletrue # Slave不处理写请求所以maxMessageSize可以略小但保持一致更安全 maxMessageSize4194304提示brokerId是整个集群里Broker的唯一标识。同一个Broker Group如broker-a下Master必须是0Slave必须是1、2、3……以此类推。如果配错了比如把Slave的brokerId也设成0启动时会报错brokerId conflict集群根本起不来。3.3 JVM参数调优不是越大越好而是恰到好处RocketMQ Broker是典型的内存敏感型应用JVM参数直接影响其吞吐和稳定性。我们线上使用的runbroker.sh中的JVM配置如下# 修改 runbroker.sh 中的 JAVA_OPT JAVA_OPT${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g JAVA_OPT${JAVA_OPT} -XX:UseG1GC -XX:G1HeapRegionSize16M -XX:G1ReservePercent15 JAVA_OPT${JAVA_OPT} -XX:MaxGCPauseMillis20 -XX:G1HeapWastePercent5 JAVA_OPT${JAVA_OPT} -XX:G1MixedGCCountTarget4 -XX:InitiatingOccupancyFraction45 JAVA_OPT${JAVA_OPT} -XX:G1MixedGCLiveThresholdPercent85 -XX:G1OldCSetRegionThresholdPercent10 JAVA_OPT${JAVA_OPT} -XX:AlwaysPreTouch -XX:MetaspaceSize256m -XX:MaxMetaspaceSize256m JAVA_OPT${JAVA_OPT} -XX:DisableExplicitGC -XX:UnlockExperimentalVMOptions JAVA_OPT${JAVA_OPT} -XX:UseG1GC -XX:G1NewSizePercent50 -XX:G1MaxNewSizePercent50这个配置经过了上百次Full GC压测验证。核心逻辑是让G1 GC的年轻代Young Gen尽可能大以容纳更多的短期对象如Netty ByteBuf减少Minor GC频率同时严格控制老年代Old Gen的增长速度避免频繁的Mixed GC。-Xmn4g意味着年轻代固定为4GB占总堆的一半这比默认的1/4要大得多。-XX:InitiatingOccupancyFraction45表示当老年代使用率达到45%时就触发Mixed GC而不是等到70%才行动这样能平滑GC压力。我们曾经试过-Xms12g -Xmx12g结果发现Minor GC次数反而增加因为年轻代变小了大量短生命周期对象被迫提前进入老年代最终导致Full GC频发。记住Broker的内存不是用来堆缓存的而是用来高效处理网络IO和消息存储的参数必须服务于这个目标。3.4 启动与验证三步走确保集群真正“活”起来启动顺序至关重要必须严格遵循先启NameServer再启Master最后启Slave。任何颠倒都会导致Broker注册失败。启动NameServer两台都执行# 在每台NameServer机器上执行 cd /opt/rocketmq nohup sh bin/mqnamesrv -c conf/namesrv.conf logs/namesrv.log 21 # 检查是否启动成功 tail -f logs/namesrv.log | grep The Name Server boot success启动Master Broker两台都执行# 在broker-a-master机器上 cd /opt/rocketmq nohup sh bin/mqbroker -c conf/broker-a-master.conf logs/broker-a-master.log 21 # 在broker-b-master机器上 cd /opt/rocketmq nohup sh bin/mqbroker -c conf/broker-b-master.conf logs/broker-b-master.log 21 # 检查Master日志确认看到Register broker to name server OK启动Slave Broker两台都执行# 在broker-a-slave机器上 cd /opt/rocketmq nohup sh bin/mqbroker -c conf/broker-a-slave.conf logs/broker-a-slave.log 21 # 在broker-b-slave机器上 cd /opt/rocketmq nohup sh bin/mqbroker -c conf/broker-b-slave.conf logs/broker-b-slave.log 21 # 检查Slave日志确认看到HAService started和Sync from master successfully验证集群是否健康不能只看进程是否存在要用官方工具mqadmin# 查看所有Broker状态在任意一台机器上执行 sh bin/mqadmin clusterList -n 10.10.1.1:9876;10.10.1.2:9876 # 输出应显示4个Broker且Status均为OK # 查看Topic路由信息假设已创建Topic test-topic sh bin/mqadmin topicRoute -n 10.10.1.1:9876;10.10.1.2:9876 -t test-topic # 输出应显示test-topic被均匀分布在broker-a和broker-b的Master/Slave上每个Broker的Queue数量相同 # 查看Broker实时统计 sh bin/mqadmin brokerStats -n 10.10.1.1:9876;10.10.1.2:9876 -b broker-a # 关注putTps写入TPS、getMinOffset最小消费位点、getMaxOffset最大写入位点是否在合理范围注意mqadmin命令的-n参数必须指定所有NameServer地址用分号隔开。如果只写一个当那个NameServer宕机时命令就会失败。这是很多新手踩的第一个坑。4. 实操过程与核心环节实现从零开始手把手完成集群搭建4.1 环境准备Linux发行版、JDK、用户与目录的标准化初始化我们统一选用CentOS 7.9内核3.10.0-1160这是经过大规模验证最稳定的版本。JDK必须是17u12或更高版本OpenJDK 17.0.28低版本会有SSL握手兼容性问题。整个过程必须用非root用户操作我们创建专用用户rocketmq# 创建用户和组 useradd rocketmq -m -d /home/rocketmq -s /bin/bash echo rocketmq:Rocket123 | chpasswd usermod -a -G wheel rocketmq # 创建标准目录结构所有机器执行 mkdir -p /data/rocketmq/{namesrv,store-a,store-a-slave,store-b,store-b-slave} chown -R rocketmq:rocketmq /data/rocketmq chmod 755 /data/rocketmq # 下载并解压RocketMQ 5.3.0官网下载校验SHA256 cd /tmp wget https://archive.apache.org/dist/rocketmq/5.3.0/rocketmq-all-5.3.0-bin-release.zip sha256sum rocketmq-all-5.3.0-bin-release.zip # 应该输出e8a7b3c...官网公布的校验值 unzip rocketmq-all-5.3.0-bin-release.zip -d /opt/ mv /opt/rocketmq-all-5.3.0-bin-release /opt/rocketmq chown -R rocketmq:rocketmq /opt/rocketmq关键点在于目录权限和用户隔离。RocketMQ的启动脚本里有su - rocketmq的逻辑如果目录不属于rocketmq用户启动时会因权限不足而失败。另外/data/rocketmq必须是独立的挂载点不能是/根分区的子目录否则磁盘满会导致系统崩溃。4.2 配置文件生成用脚本自动化杜绝手工编辑错误手工编辑6份配置文件极易出错。我们编写了一个Python脚本gen_conf.py输入参数后自动生成所有配置#!/usr/bin/env python3 # gen_conf.py import os import sys def gen_namesrv_conf(ip_list): content flistenPort9876 maxConnection5000 kvConfigPath/data/rocketmq/namesrv/kvconfig.json with open(conf/namesrv.conf, w) as f: f.write(content) print(fGenerated namesrv.conf for {ip_list}) def gen_broker_conf(broker_name, broker_id, role, namesrv_addr, store_path): content fbrokerClusterNameDefaultCluster brokerName{broker_name} brokerId{broker_id} namesrvAddr{namesrv_addr} storePathRootDir{store_path} storePathCommitLog{store_path}/commitlog storePathConsumeQueue{store_path}/consumequeue storePathIndex{store_path}/index flushDiskTypeASYNC_FLUSH brokerRole{role} slaveReadEnable{true if role SLAVE else false} maxMessageSize4194304 filename fconf/{broker_name}-{role.lower()}.conf with open(filename, w) as f: f.write(content) print(fGenerated {filename}) if __name__ __main__: if len(sys.argv) ! 2: print(Usage: python3 gen_conf.py env:prod|dev) sys.exit(1) env sys.argv[1] if env prod: namesrv_ips [10.10.1.1, 10.10.1.2] namesrv_addr ;.join([f{ip}:9876 for ip in namesrv_ips]) # Generate NameServer conf gen_namesrv_conf(namesrv_ips) # Generate Broker A Master gen_broker_conf(broker-a, 0, ASYNC_MASTER, namesrv_addr, /data/rocketmq/store-a) # Generate Broker A Slave gen_broker_conf(broker-a, 1, SLAVE, namesrv_addr, /data/rocketmq/store-a-slave) # Generate Broker B Master gen_broker_conf(broker-b, 0, ASYNC_MASTER, namesrv_addr, /data/rocketmq/store-b) # Generate Broker B Slave gen_broker_conf(broker-b, 1, SLAVE, namesrv_addr, /data/rocketmq/store-b-slave)执行python3 gen_conf.py prod就能一键生成所有5份配置文件。这个脚本的价值在于消除了人为失误比如把brokerId写错、brokerRole拼错、namesrvAddr少写一个分号。在团队协作中这个脚本就是配置的“唯一真相源”。4.3 Topic与权限配置让集群真正可用的第一步集群启动后它只是一个空壳。必须创建Topic并赋予Producer/Consumer权限才算真正可用。我们使用mqadmin创建一个名为ORDER_TOPIC的Topic它将用于订单消息# 创建Topic指定它在broker-a和broker-b上各分配4个Queue共8个Queue sh bin/mqadmin updateTopic -n 10.10.1.1:9876;10.10.1.2:9876 \ -c DefaultCluster \ -t ORDER_TOPIC \ -r 4 \ -w 4 \ -s true \ -o false # 参数解释 # -r 4: Read Queue数量Consumer可并行消费的队列数 # -w 4: Write Queue数量Producer可并行发送的队列数 # -s true: 是否允许自动创建线上环境建议false必须显式创建 # -o false: 是否是顺序Topic普通Topic设为false接着配置ACL访问控制列表这是5.3.0新增的安全特性。创建conf/plain_acl.yml# conf/plain_acl.yml global: whiteRemoteAddress: accounts: - accessKey: RocketMQAdmin secretKey: Admin123456 admin: true defaultTopicPerm: DENY defaultGroupPerm: DENY topicPerms: - topicORDER_TOPICpermSUBPUB groupPerms: - grouporder-consumer-grouppermSUB - grouporder-producer-grouppermPUB然后在broker.conf中启用ACL# 在 conf/broker-a-master.conf 和 conf/broker-b-master.conf 中添加 aclEnabletrue重启所有Broker后Producer和Consumer就必须带上accessKey和secretKey才能连接。这杜绝了未授权的客户端随意往集群里发垃圾消息是生产环境的必备防线。4.4 监控与告警用PrometheusGrafana构建可视化运维视图一个没有监控的集群就像一辆没有仪表盘的汽车。我们基于RocketMQ 5.3.0内置的Metrics Exporter搭建了一套完整的监控体系启用Broker Metrics在每个Broker的conf/broker.conf中添加metricsExporterTypeprometheus metricsExporterAddr0.0.0.0:5557这样每个Broker就会在5557端口暴露Prometheus格式的指标。部署Prometheus配置prometheus.yml抓取所有Broker和NameServer的指标scrape_configs: - job_name: rocketmq-namesrv static_configs: - targets: [10.10.1.1:9876, 10.10.1.2:9876] - job_name: rocketmq-broker static_configs: - targets: [10.10.1.3:5557, 10.10.1.4:5557, 10.10.1.5:5557, 10.10.1.6:5557]导入Grafana Dashboard使用社区维护的RocketMQ DashboardID: 13222它包含了关键指标rocketmq_broker_commitlog_disk_usage_percentCommitLog磁盘使用率超过85%就要告警扩容。rocketmq_broker_put_tps写入TPS持续低于阈值如1000可能意味着Producer异常。rocketmq_broker_get_min_offset与rocketmq_broker_get_max_offset的差值即消息堆积量差值超过100万就要触发告警。这套监控让我们能在问题发生前就介入。比如当rocketmq_broker_commitlog_disk_usage_percent曲线开始陡峭上升我们就知道某个Topic的Consumer消费太慢需要去查它的日志当rocketmq_broker_ha_slave_diff主从数据差持续大于1000就说明某个Slave同步滞后需要检查网络或磁盘IO。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”5.1 问题一Broker启动后在clusterList里看不到自己或者状态是NOT_ONLINE现象sh bin/mqadmin clusterList -n ns1:9876;ns2:9876输出里只有NameServer没有Broker或者Broker状态是NOT_ONLINE。排查思路这是网络连通性问题的典型表现。不要急着看日志先做三件事Ping测试在Broker机器上ping -c 3 10.10.1.1和ping -c 3 10.10.1.2确认能通。Telnet测试telnet 10.10.1.1 9876如果超时说明NameServer端口没开或防火墙拦截。检查Broker日志grep register broker to name server logs/broker-a-master.log如果找不到这条日志说明注册请求根本没发出去。根本原因与解决我们遇到过两次。第一次是云厂商的安全组规则没放行9876端口第二次是Broker机器的/etc/hosts文件里把127.0.0.1映射到了一个错误的hostname导致Broker用这个hostname去注册而NameServer解析不到。解决方案是在broker.conf里显式指定brokerIP110.10.1.3Broker本机真实IP绕过hostname解析。5.2 问题二Slave日志里反复出现HAConnection is closed无法同步数据现象Slave启动后日志里不断打印HAConnection is closedSync from master successfully只出现一次之后就再没同步记录。排查思路这是HA同步链路断开。重点检查Master的broker.conf里brokerRoleASYNC_MASTER是否正确。Slave的broker.conf里brokerRoleSLAVE是否正确且brokerId是否与Master同组且不为0。Master和Slave的storePathRootDir是否指向了同一块磁盘绝对不能它们必须是完全独立的路径否则会互相覆盖。根本原因与解决我们曾因Ansible脚本的一个变量错误导致broker-a-slave的storePathRootDir被错误地指向了/data/rocketmq/store-a即Master的路径。结果Slave启动时发现CommitLog目录里已经有文件就认为自己是Master拒绝作为Slave启动。解决方案是彻底删除Slave的存储目录重新创建并确保broker.conf里的路径指向正确的、空的目录。5.3 问题三Producer发送消息超时报错RemotingTimeoutException现象Producer代码里producer.send(msg)抛出org.apache.rocketmq.remoting.exception.RemotingTimeoutException。排查思路这个错误很宽泛需要层层过滤检查NameServersh bin/mqadmin clusterList是否能看到所有Broker如果看不到回到问题一。检查Topic路由sh bin/mqadmin topicRoute -n ns1:9876;ns2:9876 -t YOUR_TOPIC确认输出里有queueDatas且brokerName字段是broker-a或broker-b而不是空。检查Broker状态sh bin/mqadmin brokerStats -n ns1:9876;ns2:9876 -b broker-a查看putTps是否为0如果是说明Broker没在接收消息。根本原因与解决最常见的原因是Producer代码里namesrvAddr配置错了比如写成了localhost:9876而Producer运行在另一台机器上。另一个隐蔽的原因是Broker的brokerIP1配置的是内网IP但Producer运行在公网环境无法直连。解决方案是在Broker的broker.conf里用brokerIP0指定一个Producer能访问到的IP比如ECS的弹性公网IP并确保该IP的10911端口已放行。5.4 问题四Consumer消费速度极慢消息堆积如山现象sh bin/mqadmin topicStatus -n ns1:9876;ns2:9876 -t ORDER_TOPIC显示Diff Total堆积量高达数百万。排查思路消费慢是系统性问题要从Consumer、Broker、网络三方面看Consumer端检查Consumer代码里consumer.subscribe(ORDER_TOPIC, *)是否正确MessageListenerConcurrently的consumeMessage方法里是否有耗时操作如同步HTTP调用consumeThreadMin和consumeThreadMax线程数是否足够默认20对于高吞吐场景可能不够。Broker端sh bin/mqadmin brokerStats -n ns1:9876;ns2:9876 -b broker-a看getTps读取TPS是否远低于putTps写入TPS如果是说明Broker读取慢。网络端iftop -P 10911看Consumer机器到Broker的网络带宽是否被打满。根本原因与解决我们遇到过一次是因为Consumer的consumeMessage方法里调用了一个外部API而那个API的平均响应时间是800ms。这意味着一个Consumer线程一秒只能处理1条消息而Producer一秒发1000条堆积必然产生。解决方案是将外部调用改为异步如发到本地队列由另一个线程池处理并将consumeThreadMax调大到100。调整后消费TPS立刻从1提升到1200。5.5 问题五集群运行一周后NameServer内存暴涨OOM Killer杀死了进程现象top命令看到java进程RSS内存持续增长最终被系统OOM Killer杀死。排查思路NameServer内存泄漏是5.3.0早期版本的一个已知问题已在5.3.1修复。但即使在5.3.0也有规避方法检查kvConfigPath确认它指向的磁盘是否有足够空间如果满了NameServer会不断重试写入导致内存泄漏。检查Client连接数netstat -an | grep :9876 | wc -l如果连接数超过5000说明有Client没有正确关闭连接如Producer/Consumer没调用shutdown()。根本原因与解决我们发现是某个测试部门的Demo程序每次启动都创建一个新的Producer但从未调用shutdown()导致连接句柄一直累积。解决方案是在NameServer的JVM参数里加上-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/rocketmq/logs/heap.hprof然后用jmap分析dump文件定位泄漏源头同时在所有Client代码里强制加入
返回列表