Commit c066ea46f9881e8ccf7b7abf3b2add8a38f9ea84

Authored by YunaiV
1 parent d048daf7

mq 重构:默认的 redis 实现:

1)默认 channel 和 stream key
2)移除 enabled 开关,通过 listener 是否存在来实现
3)调整包名,为接入 rocketmq 作为 mq 实现做准备
Showing 15 changed files with 51 additions and 114 deletions
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/TenantRedisMessageInterceptor.java
1 1 package cn.iocoder.yudao.framework.tenant.core.mq;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
5   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
  4 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
6 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 7  
8 8 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/package-info.java
1 1 /**
  2 + * TODO 芋艿:调整注释
2 3 * 消息队列,基于 Redis 提供:
3 4 * 1. 基于 Pub/Sub 实现广播消费
4 5 * 2. 基于 Stream 实现集群消费
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQAutoConfiguration.java
1   -package cn.iocoder.yudao.framework.mq.config;
  1 +package cn.iocoder.yudao.framework.mq.redis.config;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.hutool.system.SystemUtil;
6 6 import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
7   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
8   -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
9   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
10   -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
11   -import cn.iocoder.yudao.framework.mq.job.RedisPendingMessageResendJob;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  8 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  9 +import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
  10 +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractChannelMessageListener;
  11 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractStreamMessageListener;
12 12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
13 13 import lombok.extern.slf4j.Slf4j;
14 14 import org.redisson.api.RedissonClient;
15 15 import org.springframework.beans.factory.annotation.Value;
16 16 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 17 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
18   -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 18 import org.springframework.context.annotation.Bean;
20 19 import org.springframework.data.redis.connection.RedisServerCommands;
21 20 import org.springframework.data.redis.connection.stream.Consumer;
... ... @@ -27,7 +26,6 @@ import org.springframework.data.redis.core.RedisTemplate;
27 26 import org.springframework.data.redis.core.StringRedisTemplate;
28 27 import org.springframework.data.redis.listener.ChannelTopic;
29 28 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
30   -import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
31 29 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
32 30 import org.springframework.scheduling.annotation.EnableScheduling;
33 31  
... ... @@ -42,7 +40,7 @@ import java.util.Properties;
42 40 @Slf4j
43 41 @EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
44 42 @AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
45   -public class YudaoMQAutoConfiguration {
  43 +public class YudaoRedisMQAutoConfiguration {
46 44  
47 45 @Bean
48 46 public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,
... ... @@ -60,7 +58,6 @@ public class YudaoMQAutoConfiguration {
60 58 */
61 59 @Bean(initMethod = "start", destroyMethod = "stop")
62 60 @ConditionalOnBean(AbstractChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
63   - @ConditionalOnProperty(prefix = "yudao.mq.redis.pubsub", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.pubsub.enable=false 禁用多租户
64 61 public RedisMessageListenerContainer redisMessageListenerContainer(
65 62 RedisMQTemplate redisMQTemplate, List<AbstractChannelMessageListener<?>> listeners) {
66 63 // 创建 RedisMessageListenerContainer 对象
... ... @@ -82,7 +79,6 @@ public class YudaoMQAutoConfiguration {
82 79 */
83 80 @Bean
84 81 @ConditionalOnBean(AbstractStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
85   - @ConditionalOnProperty(prefix = "yudao.mq.redis.stream", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.stream.enable=false 禁用多租户
86 82 public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractStreamMessageListener<?>> listeners,
87 83 RedisMQTemplate redisTemplate,
88 84 @Value("${spring.application.name}") String groupName,
... ... @@ -92,12 +88,11 @@ public class YudaoMQAutoConfiguration {
92 88  
93 89 /**
94 90 * 创建 Redis Stream 集群消费的容器
95   - * <p>
96   - * Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html
  91 + *
  92 + * 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a>
97 93 */
98 94 @Bean(initMethod = "start", destroyMethod = "stop")
99 95 @ConditionalOnBean(AbstractStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
100   - @ConditionalOnProperty(prefix = "yudao.mq.redis.stream", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.stream.enable=false 禁用多租户
101 96 public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
102 97 RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) {
103 98 RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
... ... @@ -111,8 +106,7 @@ public class YudaoMQAutoConfiguration {
111 106 .build();
112 107 // 创建 container 对象
113 108 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
114   -// StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions);
115   - DefaultStreamMessageListenerContainerX.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
  109 + StreamMessageListenerContainer.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
116 110  
117 111 // 第二步,注册监听器,消费对应的 Stream 主题
118 112 String consumerName = buildConsumerName();
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/RedisMQTemplate.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/RedisMQTemplate.java
1   -package cn.iocoder.yudao.framework.mq.core;
  1 +package cn.iocoder.yudao.framework.mq.redis.core;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4   -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
5   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
6   -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
7   -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
  4 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractChannelMessage;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractStreamMessage;
8 8 import lombok.AllArgsConstructor;
9 9 import lombok.Getter;
10 10 import org.springframework.data.redis.connection.stream.RecordId;
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/interceptor/RedisMessageInterceptor.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/interceptor/RedisMessageInterceptor.java
1   -package cn.iocoder.yudao.framework.mq.core.interceptor;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.interceptor;
2 2  
3   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
  3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 4  
5 5 /**
6 6 * {@link AbstractRedisMessage} 消息拦截器
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/job/RedisPendingMessageResendJob.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java
1   -package cn.iocoder.yudao.framework.mq.job;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.job;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
5   -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
  4 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractStreamMessageListener;
6 6 import lombok.AllArgsConstructor;
7 7 import lombok.extern.slf4j.Slf4j;
8 8 import org.redisson.api.RLock;
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/message/AbstractRedisMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/message/AbstractRedisMessage.java
1   -package cn.iocoder.yudao.framework.mq.core.message;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.message;
2 2  
3 3 import lombok.Data;
4 4  
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/pubsub/AbstractChannelMessage.java
1   -package cn.iocoder.yudao.framework.mq.core.pubsub;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.pubsub;
2 2  
3   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
  3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5  
6 6 /**
... ... @@ -11,11 +11,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
11 11 public abstract class AbstractChannelMessage extends AbstractRedisMessage {
12 12  
13 13 /**
14   - * 获得 Redis Channel
  14 + * 获得 Redis Channel,默认使用类名
15 15 *
16 16 * @return Channel
17 17 */
18 18 @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。
19   - public abstract String getChannel();
  19 + public String getChannel() {
  20 + return getClass().getSimpleName();
  21 + }
20 22  
21 23 }
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessageListener.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/pubsub/AbstractChannelMessageListener.java
1   -package cn.iocoder.yudao.framework.mq.core.pubsub;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.pubsub;
2 2  
3 3 import cn.hutool.core.util.TypeUtil;
4 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
6   -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
7   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
8 8 import lombok.Setter;
9 9 import lombok.SneakyThrows;
10 10 import org.springframework.data.redis.connection.Message;
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/stream/AbstractStreamMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractStreamMessage.java
1   -package cn.iocoder.yudao.framework.mq.core.stream;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.stream;
2 2  
3   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
  3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5  
6 6 /**
... ... @@ -11,11 +11,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
11 11 public abstract class AbstractStreamMessage extends AbstractRedisMessage {
12 12  
13 13 /**
14   - * 获得 Redis Stream Key
  14 + * 获得 Redis Stream Key,默认使用类名
15 15 *
16 16 * @return Channel
17 17 */
18 18 @JsonIgnore // 避免序列化
19   - public abstract String getStreamKey();
  19 + public String getStreamKey() {
  20 + return getClass().getSimpleName();
  21 + }
20 22  
21 23 }
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/stream/AbstractStreamMessageListener.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractStreamMessageListener.java
1   -package cn.iocoder.yudao.framework.mq.core.stream;
  1 +package cn.iocoder.yudao.framework.mq.redis.core.stream;
2 2  
3 3 import cn.hutool.core.util.TypeUtil;
4 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5   -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
6   -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
7   -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
8 8 import lombok.Getter;
9 9 import lombok.Setter;
10 10 import lombok.SneakyThrows;
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列,基于 Redis 提供:
  3 + * 1. 基于 Pub/Sub 实现广播消费
  4 + * 2. 基于 Stream 实现集群消费
  5 + */
  6 +package cn.iocoder.yudao.framework.mq.redis;
... ...
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/org/springframework/data/redis/stream/DefaultStreamMessageListenerContainerX.java deleted
1   -package org.springframework.data.redis.stream;
2   -
3   -import cn.hutool.core.util.ReflectUtil;
4   -import org.springframework.data.redis.connection.RedisConnectionFactory;
5   -import org.springframework.data.redis.connection.stream.ByteRecord;
6   -import org.springframework.data.redis.connection.stream.ReadOffset;
7   -import org.springframework.data.redis.connection.stream.Record;
8   -import org.springframework.util.Assert;
9   -
10   -import java.util.Collections;
11   -import java.util.List;
12   -import java.util.function.Function;
13   -
14   -/**
15   - * 拓展 DefaultStreamMessageListenerContainer 实现,解决 Spring Data Redis + Redisson 结合使用时,Redisson 在 Stream 获得不到数据时,返回 null 而不是空 List,导致 NPE 异常。
16   - * 对应 issue:https://github.com/spring-projects/spring-data-redis/issues/2147 和 https://github.com/redisson/redisson/issues/4006
17   - * 目前看下来 Spring Data Redis 不肯加 null 判断,Redisson 暂时也没改返回 null 到空 List 的打算,所以暂时只能自己改,哽咽!
18   - *
19   - * @author 芋道源码
20   - */
21   -public class DefaultStreamMessageListenerContainerX<K, V extends Record<K, ?>> extends DefaultStreamMessageListenerContainer<K, V> {
22   -
23   - /**
24   - * 参考 {@link StreamMessageListenerContainer#create(RedisConnectionFactory, StreamMessageListenerContainerOptions)} 的实现
25   - */
26   - public static <K, V extends Record<K, ?>> StreamMessageListenerContainer<K, V> create(RedisConnectionFactory connectionFactory, StreamMessageListenerContainer.StreamMessageListenerContainerOptions<K, V> options) {
27   - Assert.notNull(connectionFactory, "RedisConnectionFactory must not be null!");
28   - Assert.notNull(options, "StreamMessageListenerContainerOptions must not be null!");
29   - return new DefaultStreamMessageListenerContainerX<>(connectionFactory, options);
30   - }
31   -
32   - public DefaultStreamMessageListenerContainerX(RedisConnectionFactory connectionFactory, StreamMessageListenerContainerOptions<K, V> containerOptions) {
33   - super(connectionFactory, containerOptions);
34   - }
35   -
36   - /**
37   - * 参考 {@link DefaultStreamMessageListenerContainer#register(StreamReadRequest, StreamListener)} 的实现
38   - */
39   - @Override
40   - public Subscription register(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {
41   - return this.doRegisterX(getReadTaskX(streamRequest, listener));
42   - }
43   -
44   - @SuppressWarnings("unchecked")
45   - private StreamPollTask<K, V> getReadTaskX(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {
46   - StreamPollTask<K, V> task = ReflectUtil.invoke(this, "getReadTask", streamRequest, listener);
47   - // 修改 readFunction 方法
48   - Function<ReadOffset, List<ByteRecord>> readFunction = (Function<ReadOffset, List<ByteRecord>>) ReflectUtil.getFieldValue(task, "readFunction");
49   - ReflectUtil.setFieldValue(task, "readFunction", (Function<ReadOffset, List<ByteRecord>>) readOffset -> {
50   - List<ByteRecord> records = readFunction.apply(readOffset);
51   - //【重点】保证 records 不是空,避免 NPE 的问题!!!
52   - return records != null ? records : Collections.emptyList();
53   - });
54   - return task;
55   - }
56   -
57   - private Subscription doRegisterX(Task task) {
58   - return ReflectUtil.invoke(this, "doRegister", task);
59   - }
60   -
61   -}
62   -
yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1   -cn.iocoder.yudao.framework.mq.config.YudaoMQAutoConfiguration
2 1 \ No newline at end of file
  2 +cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQAutoConfiguration
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -145,12 +145,6 @@ yudao:
145 145 - cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants
146 146 - cn.iocoder.yudao.module.system.enums.ErrorCodeConstants
147 147 - cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants
148   - mq:
149   - redis:
150   - pubsub:
151   - enable: false # 是否开启 Redis pubsub 广播消费,默认为 true。这里设置成 false,可以按需开启
152   - stream:
153   - enable: false # 是否开启 Redis stream 集群消费,默认为 true。这里设置成 false,可以按需开启
154 148 tenant: # 多租户相关配置项
155 149 enable: true
156 150 ignore-urls:
... ...