重复消费(幂等性)
重复消费场景
1 consumer 返回失败
public enum ConsumeConcurrentlyStatus {
CONSUME_SUCCESS,
RECONSUME_LATER;
}
如果返回 ConsumeConcurrentlyStatus.RECONSUME_LATER,消息就会重试,就会重复消费
2 consumer 消费超时
| 模式 | 重试次数 | 重试间隔 | 队列 |
|---|---|---|---|
| 并发 | 16(默认) | 10s, 30s, 1m, 2m, 3m, 4m, 5m, 6m, 7m, 8m, 9m, 10m, 20m, 30m, 1h, 2h | 重试队列(%RETRY%消费者组名称) |
| 顺序 | 无限制(int 最大值) | 1000ms | 原始队列 |
消费者还在处理消息中,还没有结束,但是达到了重试间隔,所以消息消费会进行重试,如果是并发模式,可以通过 getReconsumeTimes() 获取到重试次数
3 producer 多次发送
生产者也可能发消息超时(进行发送重试),网络波动等原因,消息都重复发送了,消费时也就会重复了
4 广播模式
消息分发是有集群和广播两种模式,如果是广播模式,Broker 会把同一条消息投递给订阅了想通的多个消费者组,这时消费就会重复(正常情况)
消息幂等处理
消息重复消费的几率还是比较大的,在广播模式下本来就要进行重复消费(如果有多个消费者组订阅了同一个 Topic 的情况下),所以要做好消费幂等
通过唯一键比如消息ID,比如唯一业务标识等,每个消费者消费消息时,检查消息是否已被消费,可以使用 redis、事务表等方式实现
// redis 消息幂等处理示例
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
String orderId = msgs[0].getUserProperty("orderId");
// 使用Redis原子操作判断是否已处理
Boolean isNew = redisTemplate.opsForValue().setIfAbsent("order:" + orderId, "1", 24, TimeUnit.HOURS);
if (Boolean.TRUE.equals(isNew)) {
// 首次处理
orderService.processOrder(orderId);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} else {
// 已处理过,直接确认
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});

浙公网安备 33010602011771号