RocketMQ的broker节点挂掉后重启导致的消息重复消费问题解决方案

我的RocketMQ架构图如下:

 

 

 

故障描述: Broker-b所在服务器宕机8小时(1:00-9:00), 重启Broker-b后, 8小时期间产生的消息被消费者订阅消费, 由于消费者等幂条件是:2小时内相同消息(msgId相同)不重复发送,  但是此时已经超过两小时, 故 1:00-7:00 期间产生的消息被重复消费。

期望: Broker-b重启后, 消费者只订阅当前时间开始的消息, 之前的消息不再订阅。

 

处理方案1:

设置消费者订阅消息的位置为最新的位置

consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

这里要注意代码注释。这个参数只对一个新的consumeGroup第一次启动时有效。
就是说,如果是一个consumerGroup重启,他只会从自己上次消费到的offset,继续消费。这个参数是没用的。 而判断是不是一个新的ConsumerGroup是在broker端判断。
要知道,消费到哪个offset最先是存在Consumer本地的,定时和broker同步自己的消费offset。broker在判断是不是一个新的consumergroup,就是查broker端有没有这个consumergroup的offset记录。

另外,对于一个新的queue,这个参数也是没用的,都是从0开始消费。

优点: 处理简单, 修改一行即可。

缺点:需要新的消费者才生效, 故此方法对于broker中已包含了ConsumerGroup的, 不起作用。

 

处理方案2:

修改broker-b的offset, 和 broker-a的配置保持一致。

方法:

把broker-a的rocketmq路径:${userPath}/store/config/consumerOffset.json 文件拷贝到broker-b的相同位置, 重启broker-b

 

优点: 简单快捷, 不需要修改程序代码。

缺点:由于broker-a节点的消息队列一直在更新,offset偏移量一直在增加, 故复制过来到重启这段时间差内, 依然有少部分信息会被重复消费, 这只能依赖等幂代码的处理。

 

事后补救:

 1. 消费者的等幂操作的条件时长设置更长(1天或者3天, 我是用redis存储msgId, 消息量不大的情况下可以设置更长的时长)

//缓存消息ID 防止消费重复
boolean setResult = RedisUtil.setnx(msgExt.getMsgId(), 60 * 60 * 24 * 1, msgExt.getMsgId().getBytes("utf-8"));
if (!setResult) {
    // 存储不成功说明已经存储过, 直接返回成功
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}

2. 增加rocketmq宕机告警

总结:
1. 程序等幂处理依然是关键, 把防重复寄望于MQ是不可靠的, 且MQ本来就是设计高并发, 这是它的优点, 所以还是在程序等幂处理上下功夫, 防止此类不可预知的问题导致数据重复。
2. 监控很重要, 及时发现问题, 及时处理。
3. 搞清楚原理, 减少误操作。
 
 
 
 
参考链接:

 

posted @ 2020-11-10 17:21  小马哥的春天  阅读(3537)  评论(0编辑  收藏  举报