SpringBoot+Redis stream实现消息队列

本文介绍了如何利用Redis作为消息队列,特别是关注Redis5以上版本的Stream特性。文章详细讲解了下载Redis、引入SpringBoot的Redis依赖、配置消费者、消费组以及初始化Stream的过程。同时,提供了配置实体类和Redis工具类的代码示例,展示了如何生产与消费消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、前言

二、下载Redis及引入Redis依赖

三、配置消费者及消费组

四,配置Redsi及初始化stream、消费组、消费者


一、前言

相较于 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ 等重量级的消息队列中间件,Redis在需求量小的情况下,也可以作为消息中间件来使用。Redis作为消息队列使用,常见的有List、发布/订阅模型以及在Redis5以后出现的Stream。Stream相较于前两种,最大的优点就是可以持久化。

二、下载Redis及引入Redis依赖

下载Redis5以上的客户端,win版下载地址

pom中引入redis依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

三、配置消费者及消费组

application.yml中配置stream key,消费组和消费者可配置多个。

redis:
  mq:
    streams:
      # key名称
      - name:  RARSP:REPORT:READ:VS
        groups:
          # 消费组名称
          - name: VS_GROUP
            消费者名称
            consumers: VS-CONSUMER-A,VS-CONSUMER-B
      # key2
      - name: RARSP:REPORT:READ:BLC
        groups:
          - name: BLC_GROUP
            consumers: BLC-CONSUMER-A,BLC-CONSUMER-B
      # key3
      - name: RARSP:REPORT:READ:HD
        groups:
          - name: HD_GROUP
            consumers: HD-CONSUMER-A,HD-CONSUMER-B
     

自定义三个实体类RedisMqGroup、RedisMqStream、RedisMq,对应application.yml中的配置

public class RedisMqGroup {


    private String name;

    private String[] consumers;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String[] getConsumers() {
        return consumers;
    }

    public void setConsumers(String[] consumers) {
        this.consumers = consumers;
    }
}
public class RedisMqStream {
    public    String name;
    public   List<RedisMqGroup> groups;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<RedisMqGroup> getGroups() {
        return groups;
    }

    public void setGroups(List<RedisMqGroup> groups) {
        this.groups = groups;
    }
}
@EnableConfigurationProperties
@Configuration
@ConfigurationProperties(prefix = "redis.mq")
public class RedisMq {
    public   List<RedisMqStream> streams;

    public List<RedisMqStream> getStreams() {
        return streams;
    }

    public void setStreams(List<RedisMqStream> streams) {
        this.streams = streams;
    }
}

四,配置Redsi及初始化stream、消费组、消费者

@Slf4j
@Configuration
public class RedisConfiguration {

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private RedisStreamUtil redisStreamUtil;
    @Resource
    private RedisMq redisMq;


    /**
     * 处理乱码
     * @return
     */
    @Bean
    public RedisTemplate redisTemplateInit() {
        // key序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //val实例化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //value hashmap序列化
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        //key haspmap序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    public List<Subscription> subscription(RedisConnectionFactory factory){
        List<Subscription> resultList = new ArrayList<>();
        AtomicInteger index = new AtomicInteger(1);
        int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(processors, processors, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(), r -> {
            Thread thread = new Thread(r);
            thread.setName("async-stream-consumer-" + index.getAndIncrement());
            thread.setDaemon(true);
            return thread;
        });
        StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> options =
                StreamMessageListenerContainer
                .StreamMessageListenerContainerOptions
                .builder()
                 // 一次最多获取多少条消息
                .batchSize(5)
                .executor(executor)
                .pollTimeout(Duration.ofSeconds(1))
               // .errorHandler()
                .build();
        for (RedisMqStream redisMqStream :redisMq.getStreams()) {
            String streamName = redisMqStream.getName();
            RedisMqGroup redisMqGroup = redisMqStream.getGroups().get(0);

            initStream(streamName,redisMqGroup.getName());
            var listenerContainer = StreamMessageListenerContainer.create(factory,options);
            // 手动ask消息
            Subscription subscription = listenerContainer.receive(Consumer.from(redisMqGroup.getName(), redisMqGroup.getConsumers()[0]),
                    StreamOffset.create(streamName, ReadOffset.lastConsumed()), new ReportReadMqListener());
            // 自动ask消息
           /* Subscription subscription = listenerContainer.receiveAutoAck(Consumer.from(redisMqGroup.getName(), redisMqGroup.getConsumers()[0]),
                    StreamOffset.create(streamName, ReadOffset.lastConsumed()), new ReportReadMqListener());*/
            resultList.add(subscription);
            listenerContainer.start();
        }
        return resultList;
    }

    private void initStream(String key, String group){
        boolean hasKey = redisStreamUtil.hasKey(key);
        if(!hasKey){
            Map<String,Object> map = new HashMap<>(1);
            map.put("field","value");
            //创建主题
            String result = redisStreamUtil.addMap(key, map);
            //创建消费组
            redisStreamUtil.createGroup(key,group);
            //将初始化的值删除掉
            redisStreamUtil.del(key,result);
            log.info("stream:{}-group:{} initialize success",key,group);
        }
    }

}

Redis工具类

 
@Component
public class RedisStreamUtil {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 创建消费组
     * @param key stream-key值
     * @param group 消费组
     * @return java.lang.String
     */
    public String createGroup(String key, String group){
        return stringRedisTemplate.opsForStream().createGroup(key, group);
    }

    /**
     * 获取消费者信息
     * @param key stream-key值
     * @param group 消费组
     * @return org.springframework.data.redis.connection.stream.StreamInfo.XInfoConsumers
     */
    public StreamInfo.XInfoConsumers queryConsumers(String key, String group){
        return stringRedisTemplate.opsForStream().consumers(key, group);
    }

    /**
     * 添加Map消息
     * @param key stream对应的key
     * @param value 消息数据
     * @return
     */
    public String addMap(String key, Map<String, Object> value){
        return stringRedisTemplate.opsForStream().add(key, value).getValue();
    }

    /**
     * 读取消息
     * @param: key
     * @return java.util.List<org.springframework.data.redis.connection.stream.MapRecord<java.lang.String,java.lang.Object,java.lang.Object>>
     */
    public List<MapRecord<String, Object, Object>> read(String key){
        return stringRedisTemplate.opsForStream().read(StreamOffset.fromStart(key));
    }

    /**
     * 确认消费
     * @param key
     * @param group
     * @param recordIds
     * @return java.lang.Long
     */
    public Long ack(String key, String group, String... recordIds){
        return stringRedisTemplate.opsForStream().acknowledge(key, group, recordIds);
    }



    /**
     * 删除消息。当一个节点的所有消息都被删除,那么该节点会自动销毁
     * @param: key
     * @param: recordIds
     * @return java.lang.Long
     */
    public Long del(String key, String... recordIds){
        return stringRedisTemplate.opsForStream().delete(key, recordIds);
    }

    /**
     * 判断是否存在key
     * @param key
     * @return
     */
    public boolean hasKey(String key){
        Boolean aBoolean = stringRedisTemplate.hasKey(key);
        return aBoolean==null?false:aBoolean;
    }
}

五、生产消息、消费消息

生产消息代码

Map<String,Object> message = new HashMap<>(2);         
 message.put("body","消息主题" );
 message.put("sendTime", "消息发送时间");
 String streamKey = "";//stream的key值,对应application.yml中配置的
 redisStreamUtil.addMap(streamKey, message);

消费消息

@Slf4j
@Component
public class ReportReadMqListener implements StreamListener<String, MapRecord<String, String, String>> {

    @Override
    public void onMessage(MapRecord<String, String, String> message) {
          // stream的key值
          String streamKey = message.getStream();
          //消息ID
          RecordId recordId = message.getId();
          //消息内容
          Map<String, String> msg = message.getValue(); 
          //TODO 处理逻辑
         
          //逻辑处理完成后,ack消息,删除消息,group为消费组名称
          redisStreamUtil.ack(streamKey,group,recordId.getValue());
          redisStreamUtil.del(streamKey,recordId.getValue());
    }
    
}

### 黑马点评项目中的 Stream 消息队列实现 #### 1. **Stream 消息队列简介** Stream 消息队列是一种基于事件驱动的消息传递机制,通常用于分布式系统中解耦组件之间的通信。它允许生产者发送消息到特定的主题(Topic),消费者订阅这些主题并处理消息[^3]。 在黑马点评项目的上下文中,Stream 消息队列可能被用来实现实时通知、订单状态更新、评论审核等功能。这种架构能够显著提高系统的可扩展性和性能。 --- #### 2. **架构设计** ##### (1)**生产者-消费者模型** 在黑马点评项目中,Stream 消息队列采用经典的生产者-消费者模式。以下是其基本工作流程: - 生产者负责将数据(如新评论、用户行为日志等)发布到指定的 Topic 中。 - 消费者订阅该 Topic 并接收消息进行后续处理,比如触发缓存刷新、生成统计数据或发送邮件/短信提醒。 此模式通过异步方式减少服务间的依赖关系,从而提升整体稳定性[^4]。 ```java // Java 示例:Kafka Producer 发送消息至 Stream 队列 Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); producer.send(new ProducerRecord<>("topic_name", "key", "message")); producer.close(); ``` --- ##### (2)**微服务集成** 由于黑马点评是一个复杂的微服务体系,各模块之间需要高效协作。借助 Stream 消息队列,可以轻松完成跨服务的数据共享与同步操作。例如,在新增一条评价记录后,可以通过消息通知其他相关服务执行相应逻辑。 具体而言,假设存在三个主要的服务单元——`CommentService`, `CacheService` 和 `NotificationService` ——它们分别承担存储评论信息、维护内存级高速访问以及推送即时反馈的任务,则整个交互过程如下所示: 1. 用户提交了一条评论; 2. Comment Service 将这条数据持久化入库的同时向 Message Queue 投递一份副本; 3. Cache Service 接收到广播信号之后立即加载最新版本的内容以便前端快速展示给浏览者查看; 4. Notification Service 则依据预设规则筛选目标群体并通过电子邮件或者手机短讯告知他们有关动态变化情况的通知详情。 上述场景充分体现了利用流式传输技术所带来的灵活性优势所在之处[^5]。 --- #### 3. **实施教程要点** 为了成功部署并运行基于 Stream消息队列解决方案,开发者应当遵循以下几个关键步骤: - **环境搭建**: 安装配置好所需的中间件软件包 (e.g., Apache Kafka / RabbitMQ),确保网络连通状况良好无误。 ```bash sudo apt-get install kafka ``` - **编码实践**: 参考官方文档编写适配各自业务需求的应用程序接口(APIs)代码片段;注意异常捕获机制的设计以增强健壮程度。 ```python from kafka import KafkaConsumer consumer = KafkaConsumer('my_topic', group_id='my_group', bootstrap_servers=['localhost:9092']) for message in consumer: print(f"{message.topic}:{message.partition} -> {message.value.decode()}") ``` - **监控运维**: 建立健全的日志审计体系帮助定位潜在隐患问题所在位置及时修复改进措施落实到位。 --- ### 结论 综上所述,通过引入像 CORBA 这样的通用对象请求代理结构 [^1], 或者现代轻量级框架 Vue.js 来辅助开发图形界面部分 [^2], 加强后台支撑能力显得尤为重要。而选用恰当类型的 Streams Messaging System 不仅有助于简化复杂度较高的多线程并发控制难题解决途径探索研究方向提供理论指导意义非凡之外还能有效促进资源利用率最大化水平达到预期效果最佳表现形式呈现出来供广大使用者体验享受便利快捷优质服务产品带来的乐趣无穷魅力无限美好未来前景展望充满希望曙光初现黎明前夜即将迎来辉煌灿烂明天!
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值