目前主流的MQ主要是Rocketmq、kafka、Rabbitmq,Rocketmq相比于Rabbitmq、kafka具有主要优势特性有:
• 支持事务型消息(消息发送和DB操作保持两方的最终一致性,rabbitmq和kafka不支持)
• 支持结合rocketmq的多个系统之间数据最终一致性(多方事务,二方事务是前提)
• 支持18个级别的延迟消息(rabbitmq和kafka不支持)
• 支持指定次数和时间间隔的失败消息重发(kafka不支持,rabbitmq需要手动确认)
• 支持consumer端tag过滤,减少不必要的网络传输(rabbitmq和kafka不支持)
• 支持重复消费(rabbitmq不支持,kafka支持)
Rocketmq、kafka、Rabbitmq的详细对比,请参照下表格:
生产者
发送消息注意事项
Tag的使用
一个应用尽可能用一个Topic,而消息子类型则可以用tags来标识。tags可以由应用自由设置,只有生产者在发送消息设置了tags,消费方在订阅消息时才可以利用tags通过broker做消息过滤,5.x SDK 可以调用messageBuilder.setTag("messageTag"),历史版本可以调用 message.setTags("messageTag")。
Keys的使用
每个消息在业务层面一般建议映射到业务的唯一标识并设置到keys字段,方便将来定位消息丢失问题。服务器会为每个消息创建索引(哈希索引),应用可以通过topic、key来查询这条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证key尽可能唯一,这样可以避免潜在的哈希冲突。常见的设置策略使用订单Id、用户Id、请求Id等比较离散的唯一标识来处理。
日志的打印
消息发送成功或者失败要打印消息日志,用于业务排查问题。Send消息方法只要不抛异常,就代表发送成功。
消息发送失败处理方式
Producer的send方法本身支持内部重试,5.x SDK的重试逻辑参考发送重试策略:
以上策略也是在一定程度上保证了消息可以发送成功。如果业务要求消息发送不能丢,仍然需要对可能出现的异常做兜底,比如调用send同步方法发送失败时,则尝试将消息存储到db,然后由后台线程定时重试,确保消息一定到达Broker。
上述DB重试方式为什么没有集成到MQ客户端内部做,而是要求应用自己去完成,主要基于以下几点考虑:首先,MQ的客户端设计为无状态模式,方便任意的水平扩展,且对机器资源的消耗仅仅是cpu、内存、网络。其次,如果MQ客户端内部集成一个KV存储模块,那么数据只有同步落盘才能较可靠,而同步落盘本身性能开销较大,所以通常会采用异步落盘,又由于应用关闭过程不受MQ运维人员控制,可能经常会发生 kill -9 这样暴力方式关闭,造成数据没有及时落盘而丢失。第三,Producer所在机器的可靠性较低,一般为虚拟机,不适合存储重要数据。综上,建议重试过程交由应用来控制。
消费者
消费过程幂等
RocketMQ 无法避免消息重复(Exactly-Once),所以如果业务对消费重复非常敏感,务必要在业务层面进行去重处理。可以借助关系数据库进行去重。首先需要确定消息的唯一键,可以是msgId,也可以是消息内容中的唯一标识字段,例如订单Id等。在消费之前判断唯一键是否在关系数据库中存在。如果不存在则插入,并消费,否则跳过。(实际过程要考虑原子性问题,判断是否存在可以尝试插入,如果报主键冲突,则插入失败,直接跳过)
msgId一定是全局唯一标识符,但是实际使用中,可能会存在相同的消息有两个不同msgId的情况(消费者主动重发、因客户端重投机制导致的重复等),这种情况就需要使业务字段进行重复消费。
消费速度慢的处理方式
提高消费并行度
绝大部分消息消费行为都属于 IO 密集型,即可能是操作数据库,或者调用 RPC,这类消费行为的消费速度在于后端数据库或者外系统的吞吐量,通过增加消费并行度,可以提高总的消费吞吐量,但是并行度增加到一定程度,反而会下降。所以,应用必须要设置合理的并行度。 如下有几种修改消费并行度的方法:
- 同一个 ConsumerGroup 下,通过增加 Consumer 实例数量来提高并行度。可以通过加机器,或者在已有机器启动多个进程的方式。
- 提高单个 Consumer 的消费并行线程,5.x PushConsumer SDK 可以通过PushConsumerBuilder.setConsumptionThreadCount() 设置线程数,SimpleConsumer可以由业务线程自由增加并发,底层线程安全;历史版本SDK PushConsumer可以通过修改参数 consumeThreadMin、consumeThreadMax实现。
批量方式消费
某些业务流程如果支持批量方式消费,则可以很大程度上提高消费吞吐量,例如订单扣款类应用,一次处理一个订单耗时 1 s,一次处理 10 个订单可能也只耗时 2 s,这样即可大幅度提高消费的吞吐量。建议使用5.x SDK的SimpleConsumer,每次接口调用设置批次大小,一次性拉取消费多条消息。
重置位点跳过非重要消息
发生消息堆积时,如果消费速度一直追不上发送速度,如果业务对数据要求不高的话,可以选择丢弃不重要的消息。建议使用重置位点功能直接调整消费位点到指定时刻或者指定位置。
优化每条消息消费过程
举例如下,某条消息的消费过程如下:
- 根据消息从 DB 查询【数据 1】
- 根据消息从 DB 查询【数据 2】
- 复杂的业务计算
- 向 DB 插入【数据 3】
- 向 DB 插入【数据 4】
这条消息的消费过程中有4次与 DB的 交互,如果按照每次 5ms 计算,那么总共耗时 20ms,假设业务计算耗时 5ms,那么总过耗时 25ms,所以如果能把 4 次 DB 交互优化为 2 次,那么总耗时就可以优化到 15ms,即总体性能提高了 40%。所以应用如果对时延敏感的话,可以把DB部署在SSD硬盘,相比于SCSI磁盘,前者的RT会小很多。
消费打印日志
如果消息量较少,建议在消费入口方法打印消息,消费耗时等,方便后续排查问题。
new MessageListener() {
@Override
public ConsumeResult consume(MessageView messageView) {
LOGGER.info("Consume message={}", messageView);
//Do your consume process
return ConsumeResult.SUCCESS;
}
}
如果能打印每条消息消费耗时,那么在排查消费慢等线上问题时,会更方便。但如果线上环境TPS很高,不建议开启,避免日志太多影响性能。
Broker
Broker 角色
Broker 角色分为 ASYNC_MASTER(异步主机)、SYNC_MASTER(同步主机)以及SLAVE(从机)。如果对消息的可靠性要求比较严格,可以采用 SYNC_MASTER加SLAVE的部署方式。如果对消息可靠性要求不高,可以采用ASYNC_MASTER加SLAVE的部署方式。如果只是测试方便,则可以选择仅ASYNC_MASTER或仅SYNC_MASTER的部署方式。
FlushDiskType
SYNC_FLUSH(同步刷新)相比于ASYNC_FLUSH(异步处理)会损失很多性能,但是也更可靠,所以需要根据实际的业务场景做好权衡。
Broker 配置
参数名 | 默认值 | 说明 |
---|---|---|
listenPort | 10911 | 接受客户端连接的监听端口 |
namesrvAddr | null | nameServer 地址 |
brokerIP1 | 网卡的 InetAddress | 当前 broker 监听的 IP |
brokerIP2 | 跟 brokerIP1 一样 | 存在主从 broker 时,如果在 broker 主节点上配置了 brokerIP2 属性,broker 从节点会连接主节点配置的 brokerIP2 进行同步 |
brokerName | null | broker 的名称 |
brokerClusterName | DefaultCluster | 本 broker 所属的 Cluser 名称 |
brokerId | 0 | broker id, 0 表示 master, 其他的正整数表示 slave |
storePathCommitLog | $HOME/store/commitlog/ | 存储 commit log 的路径 |
storePathConsumerQueue | $HOME/store/consumequeue/ | 存储 consume queue 的路径 |
mapedFileSizeCommitLog | 1024 1024 1024(1G) | commit log 的映射文件大小 |
deleteWhen | 04 | 在每天的什么时间删除已经超过文件保留时间的 commit log |
fileReserverdTime | 72 | 以小时计算的文件保留时间 |
brokerRole | ASYNC_MASTER | SYNC_MASTER/ASYNC_MASTER/SLAVE |
flushDiskType | ASYNC_FLUSH | SYNC_FLUSH/ASYNC_FLUSH SYNC_FLUSH 模式下的 broker 保证在收到确认生产者之前将消息刷盘。ASYNC_FLUSH 模式下的 broker 则利用刷盘一组消息的模式,可以取得更好的性能。 |
Dledger快速搭建
前言
DLedger是一套基于Raft协议的分布式日志存储组件,部署 RocketMQ 时可以根据需要选择使用DLeger来替换原生的副本存储机制。本文档主要介绍如何快速构建和部署基于 DLedger 的可以自动容灾切换的 RocketMQ 集群。
1. 源码构建
构建分为两个部分,需要先构建 DLedger,然后 构建 RocketMQ。
1.1 构建 DLedger
$ git clone https://github.com/openmessaging/dledger.git
$ cd dledger
$ mvn clean install -DskipTests
1.2 构建 RocketMQ
$ git clone https://github.com/apache/rocketmq.git
$ cd rocketmq
$ git checkout -b develop origin/develop
$ mvn -Prelease-all -DskipTests clean install -U
2. 快速部署
在构建成功后
#{rocketmq-version} replace with rocketmq actual version. example: 5.0.0-SNAPSHOT
$ cd distribution/target/rocketmq-{rocketmq-version}/rocketmq-{rocketmq-version}
$ sh bin/dledger/fast-try.sh start
如果上面的步骤执行成功,可以通过 mqadmin 运维命令查看集群状态。
$ sh bin/mqadmin clusterList -n 127.0.0.1:9876
顺利的话,会看到如下内容:
(BID 为 0 的表示 Master,其余都是 Follower)
启动成功,现在可以向集群收发消息,并进行容灾切换测试了。
关闭快速集群,可以执行:
$ sh bin/dledger/fast-try.sh stop
快速部署,默认配置在 conf/dledger 里面,默认的存储路径在 /tmp/rmqstore。
3. 容灾切换
部署成功,杀掉 Leader 之后(在上面的例子中,杀掉端口 30931 所在的进程),等待约 10s 左右,用 clusterList 命令查看集群,就会发现 Leader 切换到另一个节点了。
Dledger集群搭建
本部分主要介绍如何部署自动容灾切换的 RocketMQ-on-DLedger Group。
RocketMQ-on-DLedger Group 是指一组相同名称的 Broker,至少需要 3 个节点,通过 Raft 自动选举出一个 Leader,其余节点 作为 Follower,并在 Leader 和 Follower 之间复制数据以保证高可用。 RocketMQ-on-DLedger Group 能自动容灾切换,并保证数据一致。 RocketMQ-on-DLedger Group 是可以水平扩展的,也即可以部署任意多个 RocketMQ-on-DLedger Group 同时对外提供服务。
1. 新集群部署
1.1 编写配置
每个 RocketMQ-on-DLedger Group 至少准备三台机器(本文假设为 3)。 编写 3 个配置文件,建议参考 conf/dledger 目录下的配置文件样例。 关键配置介绍:
name | 含义 | 举例 |
---|---|---|
enableDLegerCommitLog | 是否启动 DLedger | true |
dLegerGroup | DLedger Raft Group的名字,建议和 brokerName 保持一致 | RaftNode00 |
dLegerPeers | DLedger Group 内各节点的端口信息,同一个 Group 内的各个节点配置必须要保证一致 | n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 |
dLegerSelfId | 节点 id, 必须属于 dLegerPeers 中的一个;同 Group 内各个节点要唯一 | n0 |
sendMessageThreadPoolNums | 发送线程个数,建议配置成 Cpu 核数 | 16 |
这里贴出 conf/dledger/broker-n0.conf 的配置举例。
brokerClusterName = RaftCluster
brokerName=RaftNode00
listenPort=30911
namesrvAddr=127.0.0.1:9876
storePathRootDir=/tmp/rmqstore/node00
storePathCommitLog=/tmp/rmqstore/node00/commitlog
enableDLegerCommitLog=true
dLegerGroup=RaftNode00
dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913
## must be unique
dLegerSelfId=n0
sendMessageThreadPoolNums=16
1.2 启动 Broker
与老版本的启动方式一致。
$ nohup sh bin/mqbroker -c conf/dledger/xxx-n0.conf &
$ nohup sh bin/mqbroker -c conf/dledger/xxx-n1.conf &
$ nohup sh bin/mqbroker -c conf/dledger/xxx-n2.conf &
2. 旧集群升级
如果旧集群采用 Master 方式部署,则每个 Master 都需要转换成一个 RocketMQ-on-DLedger Group。
如果旧集群采用 Master-Slave 方式部署,则每个 Master-Slave 组都需要转换成一个 RocketMQ-on-DLedger Group。
2.1 杀掉旧的 Broker
可以通过 kill 命令来完成,也可以调用 bin/mqshutdown broker
。
2.2 检查旧的 Commitlog
RocketMQ-on-DLedger 组中的每个节点,可以兼容旧的 Commitlog ,但其 Raft 复制过程,只能针对新增加的消息。因此,为了避免出现异常,需要保证 旧的 Commitlog 是一致的。 如果旧的集群是采用 Master-Slave 方式部署,有可能在shutdown时,其数据并不是一致的,建议通过md5sum 的方式,检查最近的最少 2 个 Commmitlog 文件,如果发现不一致,则通过拷贝的方式进行对齐。
虽然 RocketMQ-on-DLedger Group 也可以以 2 节点方式部署,但其会丧失容灾切换能力(2n + 1 原则,至少需要3个节点才能容忍其中 1 个宕机)。
所以在对齐了 Master 和 Slave 的 Commitlog 之后,还需要准备第 3 台机器,并把旧的 Commitlog 从 Master 拷贝到 第 3 台机器(记得同时拷贝一下 config 文件夹)。
在 3 台机器准备好了之后,旧 Commitlog 文件也保证一致之后,就可以开始走下一步修改配置了。
2.3 修改配置
参考新集群部署。
2.4 重新启动 Broker
参考新集群部署。
2. 权限控制的定义与属性值
2.1权限定义
对RocketMQ的Topic资源访问权限控制定义主要如下表所示,分为以下四种
权限 | 含义 |
---|---|
DENY | 拒绝 |
ANY | PUB 或者 SUB 权限 |
PUB | 发送权限 |
SUB | 订阅权限 |
2.2 权限定义的关键属性
字段 | 取值 | 含义 |
---|---|---|
globalWhiteRemoteAddresses | *;192.168.*.*;192.168.0.1 | 全局IP白名单 |
accessKey | 字符串 | Access Key |
secretKey | 字符串 | Secret Key |
whiteRemoteAddress | *;192.168.*.*;192.168.0.1 | 用户IP白名单 |
admin | true;false | 是否管理员账户 |
defaultTopicPerm | DENY;PUB;SUB;PUB|SUB | 默认的Topic权限 |
defaultGroupPerm | DENY;PUB;SUB;PUB|SUB | 默认的ConsumerGroup权限 |
topicPerms | topic=权限 | 各个Topic的权限 |
groupPerms | group=权限 | 各个ConsumerGroup的权限 |
具体可以参考distribution/conf/plain_acl.yml配置文件
3. 支持权限控制的集群部署
在distribution/conf/plain_acl.yml配置文件中按照上述说明定义好权限属性后,打开aclEnable开关变量即可开启RocketMQ集群的ACL特性。这里贴出Broker端开启ACL特性的properties配置文件内容:
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
storePathRootDir=/data/rocketmq/rootdir-a-m
storePathCommitLog=/data/rocketmq/commitlog-a-m
autoCreateSubscriptionGroup=true
## if acl is open,the flag will be true
aclEnable=true
listenPort=10911
brokerIP1=XX.XX.XX.XX1
namesrvAddr=XX.XX.XX.XX:9876
4. 权限控制主要流程
ACL主要流程分为两部分,主要包括权限解析和权限校验。
4.1 权限解析
Broker端对客户端的RequestCommand请求进行解析,拿到需要鉴权的属性字段。 主要包括: (1)AccessKey:类似于用户名,代指用户主体,权限数据与之对应; (2)Signature:客户根据 SecretKey 签名得到的串,服务端再用SecretKey进行签名验证;
4.2 权限校验
Broker端对权限的校验逻辑主要分为以下几步: (1)检查是否命中全局 IP 白名单;如果是,则认为校验通过;否则走 2; (2)检查是否命中用户 IP 白名单;如果是,则认为校验通过;否则走 3; (3)校验签名,校验不通过,抛出异常;校验通过,则走 4; (4)对用户请求所需的权限 和 用户所拥有的权限进行校验;不通过,抛出异常; 用户所需权限的校验需要注意已下内容: (1)特殊的请求例如 UPDATE_AND_CREATE_TOPIC 等,只能由 admin 账户进行操作; (2)对于某个资源,如果有显性配置权限,则采用配置的权限;如果没有显性配置权限,则采用默认的权限;
5. 热加载修改后权限控制定义
RocketMQ的权限控制存储的默认实现是基于yml配置文件。用户可以动态修改权限控制定义的属性,而不需重新启动Broker服务节点。
6. 权限控制的使用限制
(1)如果ACL与高可用部署(Master/Slave架构)同时启用,那么需要在Broker Master节点的distribution/conf/plain_acl.yml配置文件中 设置全局白名单信息,即为将Slave节点的ip地址设置至Master节点plain_acl.yml配置文件的全局白名单中。
(2)如果ACL与高可用部署(多副本Dledger架构)同时启用,由于出现节点宕机时,Dledger Group组内会自动选主,那么就需要将Dledger Group组 内所有Broker节点的plain_acl.yml配置文件的白名单设置所有Broker节点的ip地址。
7. ACL mqadmin配置管理命令
7.1 更新ACL配置文件中“account”的属性值
该命令的示例如下:
$ sh mqadmin updateAclConfig -n 192.168.1.2:9876 -b 192.168.12.134:10911 -a RocketMQ -s 1234567809123 -t topicA=DENY,topicD=SUB -g groupD=DENY,groupB=SUB
说明:如果不存在则会在ACL Config YAML配置文件中创建;若存在,则会更新对应的“accounts”的属性值; 如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。
参数 | 取值 | 含义 |
---|---|---|
n | eg:192.168.1.2:9876 | namesrv地址(必填) |
c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) |
b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) |
a | eg:RocketMQ | Access Key值(必填) |
s | eg:1234567809123 | Secret Key值(可选) |
m | eg:true | 是否管理员账户(可选) |
w | eg:192.168.0.* | whiteRemoteAddress,用户IP白名单(可选) |
i | eg:DENY;PUB;SUB;PUB|SUB | defaultTopicPerm,默认Topic权限(可选) |
u | eg:DENY;PUB;SUB;PUB|SUB | defaultGroupPerm,默认ConsumerGroup权限(可选) |
t | eg:topicA=DENY,topicD=SUB | topicPerms,各个Topic的权限(可选) |
g | eg:groupD=DENY,groupB=SUB | groupPerms,各个ConsumerGroup的权限(可选) |
7.2 删除ACL配置文件里面的对应“account”
该命令的示例如下:
$ sh mqadmin deleteAccessConfig -n 192.168.1.2:9876 -c DefaultCluster -a RocketMQ
说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 其中,参数"a"为Access Key的值,用以标识唯一账户id,因此该命令的参数中指定账户id即可。
参数 | 取值 | 含义 |
---|---|---|
n | eg:192.168.1.2:9876 | namesrv地址(必填) |
c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) |
b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) |
a | eg:RocketMQ | Access Key的值(必填) |
7.3 更新ACL配置文件里面中的全局白名单
该命令的示例如下:
$ sh mqadmin updateGlobalWhiteAddr -n 192.168.1.2:9876 -b 192.168.12.134:10911 -g 10.10.154.1,10.10.154.2
说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。 其中,参数"g"为全局IP白名的值,用以更新ACL配置文件中的“globalWhiteRemoteAddresses”字段的属性值。
参数 | 取值 | 含义 |
---|---|---|
n | eg:192.168.1.2:9876 | namesrv地址(必填) |
c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) |
b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) |
g | eg:10.10.154.1,10.10.154.2 | 全局IP白名单(必填) |
7.4 查询集群/Broker的ACL配置文件版本信息
该命令的示例如下:
$ sh mqadmin clusterAclConfigVersion -n 192.168.1.2:9876 -c DefaultCluster
说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。
参数 | 取值 | 含义 |
---|---|---|
n | eg:192.168.1.2:9876 | namesrv地址(必填) |
c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) |
b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) |
7.5 查询集群/Broker的ACL配置文件全部内容
该命令的示例如下:
$ sh mqadmin getAccessConfigSubCommand -n 192.168.1.2:9876 -c DefaultCluster
说明:如果指定的是集群名称,则会在集群中各个broker节点执行该命令;否则会在单个broker节点执行该命令。
参数 | 取值 | 含义 |
---|---|---|
n | eg:192.168.1.2:9876 | namesrv地址(必填) |
c | eg:DefaultCluster | 指定集群名称(与broker地址二选一) |
b | eg:192.168.12.134:10911 | 指定broker地址(与集群名称二选一) |
特别注意开启Acl鉴权认证后导致Master/Slave和Dledger模式下Broker同步数据异常的问题, 在社区[4.5.1]版本中已经修复,具体的PR链接为:https://github.com/apache/rocketmq/pull/1149;
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截止时间调度器,它试图为请求提供有保证的延迟。
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));