Commit 604266d33c6b29a829282294b02d076e8fbe1b17
1 parent
0e6b4e46
fix: 解决 redis mq 消息丢失问题
Showing
1 changed file
with
14 additions
and
20 deletions
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 | }); |