Commit 604266d33c6b29a829282294b02d076e8fbe1b17

Authored by gaibu
1 parent 0e6b4e46

fix: 解决 redis mq 消息丢失问题

yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/job/RedisPendingMessageResendJob.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.mq.job; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.mq.job;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; 4 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5 import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener; 5 import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
  6 +import lombok.AllArgsConstructor;
6 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
7 import org.redisson.api.RLock; 8 import org.redisson.api.RLock;
8 import org.redisson.api.RedissonClient; 9 import org.redisson.api.RedissonClient;
@@ -22,7 +23,9 @@ import java.util.Map; @@ -22,7 +23,9 @@ import java.util.Map;
22 * 这个任务用于处理,crash 之后的消费者未消费完的消息 23 * 这个任务用于处理,crash 之后的消费者未消费完的消息
23 */ 24 */
24 @Slf4j 25 @Slf4j
  26 +@AllArgsConstructor
25 public class RedisPendingMessageResendJob { 27 public class RedisPendingMessageResendJob {
  28 +
26 private static final String LOCK_KEY = "redis:pending:msg:lock"; 29 private static final String LOCK_KEY = "redis:pending:msg:lock";
27 30
28 private final List<AbstractStreamMessageListener<?>> listeners; 31 private final List<AbstractStreamMessageListener<?>> listeners;
@@ -30,13 +33,6 @@ public class RedisPendingMessageResendJob { @@ -30,13 +33,6 @@ public class RedisPendingMessageResendJob {
30 private final String groupName; 33 private final String groupName;
31 private final RedissonClient redissonClient; 34 private final RedissonClient redissonClient;
32 35
33 - public RedisPendingMessageResendJob(List<AbstractStreamMessageListener<?>> listeners, RedisMQTemplate redisTemplate, String groupName, RedissonClient redissonClient) {  
34 - this.listeners = listeners;  
35 - this.redisTemplate = redisTemplate;  
36 - this.groupName = groupName;  
37 - this.redissonClient = redissonClient;  
38 - }  
39 -  
40 /** 36 /**
41 * 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题 37 * 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题
42 */ 38 */
@@ -57,28 +53,26 @@ public class RedisPendingMessageResendJob { @@ -57,28 +53,26 @@ public class RedisPendingMessageResendJob {
57 53
58 private void execute() { 54 private void execute() {
59 StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream(); 55 StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
60 -  
61 listeners.forEach(listener -> { 56 listeners.forEach(listener -> {
62 PendingMessagesSummary pendingMessagesSummary = ops.pending(listener.getStreamKey(), groupName); 57 PendingMessagesSummary pendingMessagesSummary = ops.pending(listener.getStreamKey(), groupName);
63 // 每个消费者的 pending 队列消息数量 58 // 每个消费者的 pending 队列消息数量
64 Map<String, Long> pendingMessagesPerConsumer = pendingMessagesSummary.getPendingMessagesPerConsumer(); 59 Map<String, Long> pendingMessagesPerConsumer = pendingMessagesSummary.getPendingMessagesPerConsumer();
65 - pendingMessagesPerConsumer.entrySet().forEach(entry -> {  
66 - String consumerName = entry.getKey();  
67 - Long pendingMessageCount = entry.getValue(); 60 + pendingMessagesPerConsumer.forEach((consumerName, pendingMessageCount) -> {
68 log.info("[processPendingMessage][消费者({}) 消息数量({})]", consumerName, pendingMessageCount); 61 log.info("[processPendingMessage][消费者({}) 消息数量({})]", consumerName, pendingMessageCount);
69 62
70 // 从消费者的 pending 队列中读取消息 63 // 从消费者的 pending 队列中读取消息
71 List<MapRecord<String, Object, Object>> records = ops.read(Consumer.from(groupName, consumerName), StreamOffset.create(listener.getStreamKey(), ReadOffset.from("0"))); 64 List<MapRecord<String, Object, Object>> records = ops.read(Consumer.from(groupName, consumerName), StreamOffset.create(listener.getStreamKey(), ReadOffset.from("0")));
72 - if (CollUtil.isNotEmpty(records)) {  
73 - for (MapRecord<String, Object, Object> record : records) {  
74 - // 重新投递消息  
75 - redisTemplate.getRedisTemplate().opsForStream().add(StreamRecords.newRecord()  
76 - .ofObject(record.getValue()) // 设置内容  
77 - .withStreamKey(listener.getStreamKey())); 65 + if (CollUtil.isEmpty(records)) {
  66 + return;
  67 + }
  68 + for (MapRecord<String, Object, Object> record : records) {
  69 + // 重新投递消息
  70 + redisTemplate.getRedisTemplate().opsForStream().add(StreamRecords.newRecord()
  71 + .ofObject(record.getValue()) // 设置内容
  72 + .withStreamKey(listener.getStreamKey()));
78 73
79 - // ack 消息消费完成  
80 - redisTemplate.getRedisTemplate().opsForStream().acknowledge(groupName, record);  
81 - } 74 + // ack 消息消费完成
  75 + redisTemplate.getRedisTemplate().opsForStream().acknowledge(groupName, record);
82 } 76 }
83 }); 77 });
84 }); 78 });