一、JVM/OS配置
1 、JVM选项
推荐使用最新发布的 JDK 版本。通过设置相同的 Xms 和 Xmx 值来防止 JVM 调整堆大小以获得更好的性能。生产环境 JVM 配置如下所示:
-server -Xms8g -Xmx8g -Xmn4g
当 JVM 是默认 8 字节对齐,建议配置最大堆内存不要超过 32 G,否则会影响 JVM 的指针压缩技术,浪费内存。
如果您不关心 RocketMQ Broker 的启动时间,还有一种更好的选择,就是通过 “预触摸” Java 堆以确保在JVM初始化期间每个页面都将被分配。那些不关心启动时间的人可以启用它:
-XX:+AlwaysPreTouch
信息
生产环境集群 Broker 一般建议配置足够的内存,避免使用小规格内存机器部署。因为Broker是重度依赖内存PageCache做性能优化的,内存过小可能造成性能不稳定。
禁用偏置锁定可能会减少 JVM 暂停:
-XX:-UseBiasedLocking
垃圾回收,建议使用 JDK 1.8 自带的 G1 收集器:
-XX:+UseG1GC
-XX:G1HeapRegionSize=16m
-XX:G1ReservePercent=25
-XX:InitiatingHeapOccupancyPercent=30
这些 GC 选项看起来有点激进,但事实证明它在我们的生产环境中具有良好的性能。
另外不要把 -XX:MaxGCPauseMillis 的值设置太小,否则 JVM 将使用一个小的年轻代来实现这个目标,这将导致非常频繁的 minor GC,所以建议使用 rolling GC 日志文件:
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=30m
如果写入 GC 文件会增加代理的延迟,可以考虑将 GC 日志文件重定向到内存文件系统:
-Xloggc:/dev/shm/mq_gc_%p.log123
2 、Linux内核参数
os.sh 脚本在 bin 文件夹中列出了许多内核参数,可以进行微小的更改然后用于生产用途。下面的参数需要注意,更多细节请参考 /proc/sys/vm/*的 文档
- vm.extra_free_kbytes 告诉 VM 在后台回收(kswapd)启动的阈值与直接回收(通过分配进程)的阈值之间保留额外的可用内存。RocketMQ 使用此参数来避免内存分配中的长延迟。(与具体内核版本相关)
- vm.min_free_kbytes 如果将其设置为低于 1024 KB,将会巧妙的将系统破坏,并且系统在高负载下容易出现死锁。
- vm.max_map_count 限制一个进程可能具有的最大内存映射区域数。RocketMQ 将使用 MMAP 加载 CommitLog 和 ConsumeQueue,因此建议将为此参数设置较大的值。
- vm.swappiness 定义内核交换内存页面的积极程度。较高的值会增加攻击性,较低的值会减少交换量。建议将值设置为 10 来避免交换延迟。
- File descriptor limits RocketMQ 需要为文件( CommitLog 和 ConsumeQueue )和网络连接打开文件描述符。我们建议设置文件描述符的值为 655350。
- Disk scheduler RocketMQ建议使用I/O截止时间调度器,它试图为请求提供有保证的延迟。
二、订阅关系一致
前言
订阅关系是 RocketMQ 领域模型中非常重要的环节,用于表达消费者消费消息的控制元数据,完整的概念请参考订阅关系模型。
订阅关系一致是指,同一个消费者组下所有消费者实例所订阅的Topic、Tag必须完全一致。如果订阅关系(消费者分组名-Topic-Tag)不一致,会导致消费消息紊乱,甚至消息丢失。
1、 正确订阅关系示例
1.1 订阅的Topic一样,且过滤表达式一致
如下图所示,同一 ConsumerGroup 下的三个Consumer实例C1、C2和C3分别都订阅了TopicA,且订阅TopicA的Tag也都是Tag1,符合订阅关系一致原则。
正确示例代码一
C1、C2、C3的订阅关系一致,即C1、C2、C3订阅消息的代码必须完全一致,代码示例如下:
PushConsumer consumer1 = provider.newPushConsumerBuilder().setConsumerGroup("GroupA").build();
consumer1.subscribe("TopicA", new FilterExpression("TagA", FilterExpressionType.TAG));
PushConsumer consumer2 = provider.newPushConsumerBuilder().setConsumerGroup("GroupA").build();
consumer2.subscribe("TopicA", new FilterExpression("TagA", FilterExpressionType.TAG));
PushConsumer consumer3 = provider.newPushConsumerBuilder().setConsumerGroup("GroupA").build();
consumer3.subscribe("TopicA", new FilterExpression("TagA", FilterExpressionType.TAG));
信息
RocketMQ 强调订阅关系一致,核心是指相同 ConsumerGroup 的每个 Consumer 之间一致,因为在服务端视角看来一个 Group 下的所有 Consumer 都应该是相同的副本逻辑。
强调订阅关系一致,并不是指一个 Consumer 不能订阅多个Topic,每个 Consumer 仍然可以按照需要订阅多个 Topic,但前提是相同消费者分组下的 Consumer 要一致。
2、 订阅关系不一致的排查
问题描述
在使用 Apache RocketMQ 时,可能会出现订阅关系不一致的情况,具体的问题现象如下:
- Apache RocketMQ 控制台中订阅关系是否一致显示为否。
- 消费者(Consumer)实例未收到订阅的消息。
请参考以下步骤进行检查
您可在消息Apache RocketMQ的控制台或者CLi工具查看指定Group的订阅关系是否一致。若查询结果不一致,请参见本文(3 常见订阅关系不一致问题)排查Consumer实例的消费代码。
- 检查您Consumer实例中与订阅相关的配置代码,确保配置同一个 ConsumerGroup 的所有Consumer实例均订阅相同的Topic及Tag。
- 使用控制台或者Cli命令ConsumerConnection 查看生效的订阅关系是否一致。
- 测试并确认消息能够被预期的Consumer实例所消费。
3 、常见订阅关系不一致问题
3.1 同一ConsumerGroup下的Consumer实例订阅的Topic不同(3.x、4.x SDK适用)
在早期3.x/4.x 版本的SDK中,如下图所示,同一 ConsumerGroup 下的三个Consumer实例C1、C2和C3分别订阅了TopicA、TopicB和TopicC,订阅的Topic不一致,不符合订阅关系一致性原则。
备注
5.x版本SDK 已经支持同一个 ConsumerGroup 下的Consumer实例订阅不同的Topic。
3.2 同一 ConsumerGroup 下的 Consumer 实例订阅的Topic相同,但订阅的Tag不一致
如下图所示,同一 ConsumerGroup 下的三个Consumer实例C1、C2和C3分别都订阅了TopicA,但是C1订阅TopicA的Tag为Tag1,C2和C3订阅的TopicA的Tag为Tag2,订阅同一Topic的Tag不一致,不符合订阅关系一致性原则。
错误示例代码二
-
Consumer实例2-1:
PushConsumer consumer1 = provider.newPushConsumerBuilder().setConsumerGroup("GroupA").build(); consumer1.subscribe("TopicA", new FilterExpression("Tag1", FilterExpressionType.TAG));
-
Consumer实例2-2:
PushConsumer consumer2 = provider.newPushConsumerBuilder().setConsumerGroup("GroupA").build(); consumer2.subscribe("TopicA", new FilterExpression("Tag2", FilterExpressionType.TAG));
-
Consumer实例2-3:
PushConsumer consumer3 = provider.newPushConsumerBuilder().setConsumerGroup("GroupA").build(); consumer3.subscribe("TopicA", new FilterExpression("Tag2", FilterExpressionType.TAG));