RocketMQ最佳实践-源自官网

本文详细介绍了RocketMQ的生产者、消费者、Broker和NameServer的最佳实践。在生产者部分,强调了Tags、Keys的使用和日志打印的重要性。消费者部分讨论了幂等消费和处理速度慢的策略,包括消费并行度提升、批量消费等。此外,还提到了Broker的角色、配置和NameServer的寻址方式。

最佳实践

1 生产者

1.1 发送消息注意事项

a) Tags的使用

一个应用尽可能用一个Topic,而消息子类型则可以用tags来标识。tags可以由应用自由设置,只有生产者在发送消息设置了tags,消费方在订阅消息时才可以利用tags通过broker做消息过滤:message.setTags(“TagA”)。

b) Keys的使用

每个消息在业务层面的唯一标识码要设置到keys字段,方便将来定位消息丢失问题。服务器会为每个消息创建索引(哈希索引),应用可以通过topic、key来查询这条消息内容,以及消息被谁消费。由于是哈希索引,请务必保证key尽可能唯一,这样可以避免潜在的哈希冲突。

   // 订单Id   
   String orderId = "20034568923546";   
   message.setKeys(orderId);   
c) 日志的打印

消息发送成功或者失败要打印消息日志,务必要打印SendResult和key字段。send消息方法只要不抛异常,就代表发送成功。发送成功会有多个状态,在sendResult里定义。以下对每个状态进行说明:

状态说明
SEND_OK消息成功发到了Broker,是否已刷盘、已同步到slave要看Broker的配置
FLUSH_DISK_TIMEOUTBroker配置同步刷盘,但超时了
FLUSH_SLAVE_TIMEOUTBroker配置了同步复制到Slave,但超时了
SLAVE_NOT_AVAILABLESYNC_MASTER情况下,无Slave可用

1.2 消息发送失败处理方式

Producer的send方法本身支持内部重试,重试逻辑如下:
时间、次数双重限制,在规定时间内(sendMsgTimeout,默认10秒)最多重试规定次数(默认2),类比车子4年10万公里,4年、10万公里任何一个先到都不会再重试了

以上策略也是在一定程度上保证了消息可以发送成功。如果业务对消息可靠性要求比较高,还需业务系统自己处理,如send失败的消息存储到db,再以定时任务扫描重发。

1.3 可以的话,选择oneway形式发送

通常消息的发送是这样一个过程:

  • 客户端发送请求到服务器
  • 服务器处理请求
  • 服务器向客户端返回应答

所以,一次消息发送的耗时时间是上述三个步骤的总和,而某些场景要求耗时非常短,但是对可靠性要求并不高,例如日志收集类应用,此类应用可以采用oneway形式调用,oneway形式只发送请求不等待应答,而发送请求在客户端实现层面仅仅是一个操作系统系统调用的开销,即将数据写入客户端的socket缓冲区,此过程耗时通常在微秒级。

2 消费者

2.1 消费过程幂等

RocketMQ无法避免消息重复(Exactly-Once),所以如果业务对消费重复非常敏感,务必要在业务层面进行去重处理。可以借助关系数据库进行去重。首先需要确定消息的唯一键,可以是msgId,或者消息体中的字段

msgId一定是全局唯一标识符,但考虑生产方重发(上面提到的定时任务重发),最好使用msgKey来去重,msgKey需业务系统自己定义,自己保障唯一性。

2.1.1 Kafka是怎么保证Exactly-Once的

Kafka是怎么保证Exactly-Once的呢?
Kafka只能保证Producer的Exactly-Once,Consumer依然要业务系统自己保证
Kafka会给每一个Producer分配一个PID,每一个Producer会给自己发的每一条消息都分配一个单调递增的序列号,Broker根据PID和序列号来去重
如果消息序号比 Broker 维护的序号大 1 以上,说明中间有数据尚未写入,此时 Broker 拒绝该消息。
如果消息序号小于等于 Broker 维护的序号,说明该消息已被保存,Broker直接丢弃该消息。

2.2 消费速度慢的处理方式

a) 提高消费并行度

IO密集型的消费方,可以适当的增加并行度,适当的增加consumer的实例(注意超过队列数的实例是无效的),增加并行度的方式:

  • 增加消费者实例
  • 加大consumeThreadMin、consumeThreadMax
b)批量消费

修改Consumer的consumeMessageBatchMaxSize(默认是1)参数,一次批量处理多个消息。这种方式适合消息可以批量消费且批量速度大于单个速度的,如将mq过来的消息保存到数据库,批量保存的效率是要优于单条保存的

c)跳过非必要消息

设置消费者从队列的尾部开始消费消息。如果评估消息不重要的话,可以将消息丢弃,如果消息不能丢弃,则通过其它的补偿机制来处理

d)提高单条消息的处理速度

性能优化的事情

2.3 消费打印日志

如果消息量较少,建议在消费入口方法打印消息,消费耗时等,方便后续排查问题。同时,在其它的入口,如Controller,都增加一个日志打印,会极大的方便问题排查

2.4 其他消费建议

a) 关于消费者和订阅

第一件需要注意的事情是,不同的消费者组可以独立的消费一些 topic,并且每个消费者组都有自己的消费偏移量,请确保同一组内的每个消费者订阅信息保持一致。

b) 关于有序消息

消费者将锁定每个消息队列,以确保他们被逐个消费,虽然这将会导致性能下降,但是当你关心消息顺序的时候会很有用。我们不建议抛出异常,你可以返回 ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT 作为替代。

c) 关于并发消费

顾名思义,消费者将并发消费这些消息,建议你使用它来获得良好性能,我们不建议抛出异常,你可以返回 ConsumeConcurrentlyStatus.RECONSUME_LATER 作为替代。

d) 关于消费状态Consume Status

对于并发的消费监听器,你可以返回 RECONSUME_LATER 来通知消费者现在不能消费这条消息,并且希望可以稍后重新消费它。然后,你可以继续消费其他消息。对于有序的消息监听器,因为你关心它的顺序,所以不能跳过消息,但是你可以返回SUSPEND_CURRENT_QUEUE_A_MOMENT 告诉消费者等待片刻。

e) 关于Blocking

不建议阻塞监听器,因为它会阻塞线程池,并最终可能会终止消费进程

f) 关于线程数设置

消费者使用 ThreadPoolExecutor 在内部对消息进行消费,所以你可以通过设置 setConsumeThreadMin 或 setConsumeThreadMax 来改变它。

g) 关于消费位点

当建立一个新的消费者组时,需要决定是否需要消费已经存在于 Broker 中的历史消息CONSUME_FROM_LAST_OFFSET 将会忽略历史消息,并消费之后生成的任何消息。CONSUME_FROM_FIRST_OFFSET 将会消费每个存在于 Broker 中的信息。你也可以使用 CONSUME_FROM_TIMESTAMP 来消费在指定时间戳后产生的消息。

3 Broker

3.1 Broker 角色

Broker 角色分为

  • ASYNC_MASTER 异步主机
  • SYNC_MASTER 同步主机
  • SLAVE 从机

如果对消息的可靠性要求比较严格,可以采用 SYNC_MASTERSLAVE的部署方式。如果对消息可靠性要求不高,可以采用ASYNC_MASTERSLAVE的部署方式。如果只是测试方便,则可以选择仅ASYNC_MASTER或仅SYNC_MASTER的部署方式。

3.2 FlushDiskType

SYNC_FLUSH(同步刷新)相比于ASYNC_FLUSH(异步处理)会损失很多性能,但是也更可靠,所以需要根据实际的业务场景做好权衡。

基于SLAVE同步方式、刷盘方式,可以综合考虑。SYNC_MASTER会将数据实时同步到slave,ASYNC_FLUSH则不会实时刷盘。两者结合,可以兼顾消息的可靠性和性能:如果master宕机了,数据在slave有备份,slave宕机了则不受影响,只有master和slave都宕机了才会丢失数据

3.3 Broker 配置

参数名默认值说明
listenPort10911接受客户端连接的监听端口
namesrvAddrnullnameServer 地址
brokerIP1网卡的 InetAddress当前 broker 监听的 IP
brokerIP2跟 brokerIP1 一样存在主从 broker 时,如果在 broker 主节点上配置了 brokerIP2 属性,broker 从节点会连接主节点配置的 brokerIP2 进行同步
brokerNamenullbroker 的名称
brokerClusterNameDefaultCluster本 broker 所属的 Cluser 名称
brokerId0broker id, 0 表示 master, 其他的正整数表示 slave
storePathCommitLog$HOME/store/commitlog/存储 commit log 的路径
storePathConsumerQueue$HOME/store/consumequeue/存储 consume queue 的路径
mappedFileSizeCommitLog1024 * 1024 * 1024(1G)commit log 的映射文件大小
deleteWhen04在每天的什么时间删除已经超过文件保留时间的 commit log
fileReservedTime72以小时计算的文件保留时间
brokerRoleASYNC_MASTERSYNC_MASTER/ASYNC_MASTER/SLAVE
flushDiskTypeASYNC_FLUSHSYNC_FLUSH/ASYNC_FLUSH SYNC_FLUSH 模式下的 broker 保证在收到确认生产者之前将消息刷盘。ASYNC_FLUSH 模式下的 broker 则利用刷盘一组消息的模式,可以取得更好的性能。

brokerIP1、brokerIP2在虚拟机中非常有用。测试时,虚拟机通常会设置两张网卡,一张NAT用于访问外网,一张HostOnly用于PC访问虚拟机,此时就需要将brokerIP1设置为HostOnly的网卡,否则NameServer会将NAT的IP给client,这将导致PC无法访问Broker

4 NameServer

RocketMQ 中,Name Servers 被设计用来做简单的路由管理。其职责包括:

  • Brokers 定期向每个NameServer注册路由数据。
  • NameServer为客户端,包括生产者,消费者和命令行客户端提供最新的路由信息。

简单来说,NameServer就是RocketMQ的简化版Eureka

5.1 客户端寻址方式

RocketMQ可以令客户端找到Name Server, 然后通过Name Server再找到Broker。如下所示有多种配置方式,优先级由高到低,高优先级会覆盖低优先级。

  • 代码中指定Name Server地址,多个namesrv地址之间用分号分割
producer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876");  
consumer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876");
  • Java启动参数中指定Name Server地址
-Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876  
  • 环境变量指定Name Server地址
export   NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876   
  • HTTP静态服务器寻址(默认)

客户端启动后,会定时访问一个静态HTTP服务器,地址如下:http://jmenv.tbsite.net:8080/rocketmq/nsaddr,这个URL的返回内容如下:

192.168.0.1:9876;192.168.0.2:9876   

客户端默认每隔2分钟访问一次这个HTTP服务器,并更新本地的Name Server地址。URL已经在代码中硬编码,可通过修改/etc/hosts文件来改变要访问的服务器,例如在/etc/hosts增加如下配置:

10.232.22.67    jmenv.taobao.net   

推荐使用HTTP静态服务器寻址方式,好处是客户端部署简单,且Name Server集群可以热升级。

5.2 客户端配置

DefaultMQProducer、TransactionMQProducer、DefaultMQPushConsumer、DefaultMQPullConsumer都继承于ClientConfig类,ClientConfig为客户端的公共配置类。客户端的配置都是get、set形式,每个参数都可以用spring来配置,也可以在代码中配置,例如namesrvAddr这个参数可以这样配置,producer.setNamesrvAddr(“192.168.0.1:9876”),其他参数同理。

1 客户端的公共配置
参数名默认值说明
namesrvAddrName Server地址列表,多个NameServer地址用分号隔开
clientIP本机IP客户端本机IP地址,某些机器会发生无法识别客户端IP地址情况,需要应用在代码中强制指定
instanceNameDEFAULT客户端实例名称,客户端创建的多个Producer、Consumer实际是共用一个内部实例(这个实例包含网络连接、线程资源等)
clientCallbackExecutorThreads4通信层异步回调线程数
pollNameServerInteval30000轮询Name Server间隔时间,单位毫秒
heartbeatBrokerInterval30000向Broker发送心跳间隔时间,单位毫秒
persistConsumerOffsetInterval5000持久化Consumer消费进度间隔时间,单位毫秒
2 Producer配置
参数名默认值说明
producerGroupDEFAULT_PRODUCERProducer组名,多个Producer如果属于一个应用,发送同样的消息,则应该将它们归为同一组
createTopicKeyTBW102在发送消息时,自动创建服务器不存在的topic,需要指定Key,该Key可用于配置发送消息所在topic的默认路由。
defaultTopicQueueNums4在发送消息,自动创建服务器不存在的topic时,默认创建的队列数
sendMsgTimeout10000发送消息超时时间,单位毫秒
compressMsgBodyOverHowmuch4096消息Body超过多大开始压缩(Consumer收到消息会自动解压缩),单位字节
retryAnotherBrokerWhenNotStoreOKFALSE如果发送消息返回sendResult,但是sendStatus!=SEND_OK,是否重试发送
retryTimesWhenSendFailed2如果消息发送失败,最大重试次数,该参数只对同步发送模式起作用
maxMessageSize4MB客户端限制的消息大小,超过报错,同时服务端也会限制,所以需要跟服务端配合使用。
transactionCheckListener事务消息回查监听器,如果发送事务消息,必须设置
checkThreadPoolMinSize1Broker回查Producer事务状态时,线程池最小线程数
checkThreadPoolMaxSize1Broker回查Producer事务状态时,线程池最大线程数
checkRequestHoldMax2000Broker回查Producer事务状态时,Producer本地缓冲请求队列大小
RPCHooknull该参数是在Producer创建时传入的,包含消息发送前的预处理和消息响应后的处理两个接口,用户可以在第一个接口中做一些安全控制或者其他操作。
3 PushConsumer配置
参数名默认值说明
consumerGroupDEFAULT_CONSUMERConsumer组名,多个Consumer如果属于一个应用,订阅同样的消息,且消费逻辑一致,则应该将它们归为同一组
messageModelCLUSTERING消费模型支持集群消费和广播消费两种
consumeFromWhereCONSUME_FROM_LAST_OFFSETConsumer启动后,默认从上次消费的位置开始消费,这包含两种情况:一种是上次消费的位置未过期,则消费从上次中止的位置进行;一种是上次消费位置已经过期,则从当前队列第一条消息开始消费
consumeTimestamp半个小时前只有当consumeFromWhere值为CONSUME_FROM_TIMESTAMP时才起作用。
allocateMessageQueueStrategyAllocateMessageQueueAveragelyRebalance算法实现策略
subscription订阅关系
messageListener消息监听器
offsetStore消费进度存储
consumeThreadMin10消费线程池最小线程数
consumeThreadMax20消费线程池最大线程数
consumeConcurrentlyMaxSpan2000单队列并行消费允许的最大跨度
pullThresholdForQueue1000拉消息本地队列缓存消息最大数
pullInterval0拉消息间隔,由于是长轮询,所以为0,但是如果应用为了流控,也可以设置大于0的值,单位毫秒
consumeMessageBatchMaxSize1批量消费,一次消费多少条消息
pullBatchSize32批量拉消息,一次最多拉多少条
4 PullConsumer配置
参数名默认值说明
consumerGroupDEFAULT_CONSUMERConsumer组名,多个Consumer如果属于一个应用,订阅同样的消息,且消费逻辑一致,则应该将它们归为同一组
brokerSuspendMaxTimeMillis20000长轮询,Consumer拉消息请求在Broker挂起最长时间,单位毫秒
consumerTimeoutMillisWhenSuspend30000长轮询,Consumer拉消息请求在Broker挂起超过指定时间,客户端认为超时,单位毫秒
consumerPullTimeoutMillis10000非长轮询,拉消息超时时间,单位毫秒
messageModelBROADCASTING消息支持两种模式:集群消费和广播消费
messageQueueListener监听队列变化
offsetStore消费进度存储
registerTopics注册的topic集合
allocateMessageQueueStrategyAllocateMessageQueueAveragelyRebalance算法实现策略
5 Message数据结构
字段名默认值说明
Topicnull必填,消息所属topic的名称
Bodynull必填,消息体
Tagsnull选填,消息标签,方便服务器过滤使用。目前只支持每个消息设置一个tag
Keysnull选填,代表这条消息的业务关键词,服务器会根据keys创建哈希索引,设置后,可以在Console系统根据Topic、Keys来查询消息,由于是哈希索引,请尽可能保证key唯一,例如订单号,商品Id等。
Flag0选填,完全由应用来设置,RocketMQ不做干预
DelayTimeLevel0选填,消息延时级别,0表示不延时,大于0会延时特定的时间才会被消费
WaitStoreMsgOKTRUE选填,表示消息是否在服务器落盘后才返回应答。

6 系统配置

本小节主要介绍系统(JVM/OS)相关的配置。

6.1 JVM选项

推荐使用最新发布的JDK 1.8版本。通过设置相同的Xms和Xmx值来防止JVM调整堆大小以获得更好的性能。简单的JVM配置如下所示:

​ ​-server -Xms8g -Xmx8g -Xmn4g ​


如果您不关心RocketMQ Broker的启动时间,还有一种更好的选择,就是通过“预触摸”Java堆以确保在JVM初始化期间每个页面都将被分配。那些不关心启动时间的人可以启用它:
​ -XX:+AlwaysPreTouch
禁用偏置锁定可能会减少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   

6.2 Linux内核参数

os.sh脚本在bin文件夹中列出了许多内核参数,可以进行微小的更改然后用于生产用途。下面的参数需要注意,更多细节请参考/proc/sys/vm/*的文档

  • vm.extra_free_kbytes,告诉VM在后台回收(kswapd)启动的阈值与直接回收(通过分配进程)的阈值之间保留额外的可用内存。RocketMQ使用此参数来避免内存分配中的长延迟。(与具体内核版本相关)
  • vm.min_free_kbytes,如果将其设置为低于1024KB,将会巧妙的将系统破坏,并且系统在高负载下容易出现死锁。
  • vm.max_map_count,限制一个进程可能具有的最大内存映射区域数。RocketMQ将使用mmap加载CommitLog和ConsumeQueue,因此建议将为此参数设置较大的值。(agressiveness --> aggressiveness)
  • vm.swappiness,定义内核交换内存页面的积极程度。较高的值会增加攻击性,较低的值会减少交换量。建议将值设置为10来避免交换延迟。
  • File descriptor limits,RocketMQ需要为文件(CommitLog和ConsumeQueue)和网络连接打开文件描述符。我们建议设置文件描述符的值为655350。
  • Disk scheduler,RocketMQ建议使用I/O截止时间调度器,它试图为请求提供有保证的延迟。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值