最佳实践
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_TIMEOUT | Broker配置同步刷盘,但超时了 |
FLUSH_SLAVE_TIMEOUT | Broker配置了同步复制到Slave,但超时了 |
SLAVE_NOT_AVAILABLE | SYNC_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)跳过非必要消息
设置消费者从队列的尾部开始消费消息。如果评估消息不重要的话,可以将消息丢弃,如果消息不能丢弃,则通过其它的