Redis实现消息队列

《框架封装 · Redis 事件监听》 企业实战开发中,事件监听的运用场景非常多,当某事件发生的时候,会触发某个响应处理,其主要优势体现在多负载实例的场景下。与前几篇博文《知识点扫盲 · 监听器 Listener》《后端程序猿 · 基于 Lettuce 实现缓存容错策略》提到的观察者模式、发布订阅模式等,有异曲同工之妙。本篇文章先介绍一下,框架封装人员如何处理事件监听场景,默认基于实现,下面以此技术加以说明。上文介绍了框架封装人员,如何处理事件监听逻辑,提供了一些思路分享。 阅读详情

前言

Redis可以通过发布订阅模式、轮询机制实现消息队列。

由于没有消息持久化与 ACK 的保证,所以,Redis 的发布订阅功能并不可靠。这也就导致了它的应用场景很有限,建议用于实时与可靠性要求不高的场景。


一、Redis发布订阅

​​​​​​1.Redis发布订阅模式实现原理

服务器中维护着一个pubsub_channels字典,所有的频道和订阅关系都存储在这里。字典的键为频道的名称,而值为订阅频道的客户端链表。

1. 当有新的客户端订阅某个频道时,会发生两种情况中的一种:

(1)如果频道已经存在,则新的客户端会添加到pubsub_channels对应频道的链表末尾

(2)如果频道原本不存在,则会为频道创建一个键,该客户端成为链表的第一个元素

2. 当一个客户端退订一个频道的时候:

pubsub_channels对应键的链表会删除该客户端

3. 发送信息

服务器会遍历pubsub_channels中对应键的链表,向每一个客户端发送信息

服务器还维护着一个pubsub_patterns链表,链表的pattern属性记录了被订阅的模式,而client属性记录了订阅模式的客户端

1. 当有新的客户端订阅某个模式的时,会进行如下步骤:

(1)创建一个链表节点,pattern属性记录订阅的模式,client记录订阅模式的客户端

(2)将这个链表节点添加到pubsub_patterns链表中

2. 当一个客户端退订某一个模式的时候:

服务器遍历pubsob_patterns找到对应的pattern同时也是对应该client客户端的节点,将改节点删除

3. 发送信息

服务器遍历pubsub_channels,查找与channels频道相匹配的模式麻将消息发送给订阅了这些模式的客户端。

2.引入依赖

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

3.redis配置

public class RedisConfig {
    //Key的过期时间
    private Duration timeToLive = Duration.ofDays(1);

    /**
     * redis模板,存储关键字是字符串,值jackson2JsonRedisSerializer是序列化后的值
     *
     * @param
     * @return org.springframework.data.redis.core.RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //使用StringRedisSerializer来序列化和反序列化redis的key值
        RedisSerializer redisSerializer = new StringRedisSerializer();
        //key
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        //value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.
                defaultCacheConfig().
                entryTtl(this.timeToLive).			//Key过期时间 1天
                serializeKeysWith(RedisSerializationContext.SerializationPair.
                fromSerializer(new StringRedisSerializer())).
                serializeValuesWith(RedisSerializationContext.SerializationPair.
                        fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

4.redis监听

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

import java.util.concurrent.CountDownLatch;


@Configuration
public class RedisMessageListener {
    /**
     * 创建连接工厂
     *
     * @param connectionFactory
     * @param listenerAdapter
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                   MessageListenerAdapter listenerAdapter,
                                                   MessageListenerAdapter listenerAdapterWang,
                                                   MessageListenerAdapter listenerAdapterTest2) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //(不同的监听器可以收到同一个频道的信息)接受消息的频道
        container.addMessageListener(listenerAdapter, new PatternTopic("phone"));

        container.addMessageListener(listenerAdapterWang, new PatternTopic("phone"));

        container.addMessageListener(listenerAdapterTest2, new PatternTopic("phoneTest2"));
        return container;
    }


    /**
     * 绑定消息监听者和接收监听的方法
     *
     * @param receiver
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter(ReceiverRedisMessage receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    @Bean
    public MessageListenerAdapter listenerAdapterWang(ReceiverRedisMessage receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessageWang");
    }
   /**
     * 绑定消息监听者和接收监听的方法
     *
     * @param receiver
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapterTest2(ReceiverRedisMessage receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage2");
    }

    /**
     * 注册订阅者
     *
     * @param latch
     * @return
     */
    @Bean
    ReceiverRedisMessage receiver(CountDownLatch latch) {
        return new ReceiverRedisMessage(latch);
    }


    /**
     * 计数器,用来控制线程
     *
     * @return
     */
    @Bean
    public CountDownLatch latch() {
        return new CountDownLatch(1);//指定了计数的次数 1
    }
}

5.redis消息接收类

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.concurrent.CountDownLatch;


public class ReceiverRedisMessage {
    private CountDownLatch latch;

    @Autowired
    public ReceiverRedisMessage(CountDownLatch latch) {
        this.latch = latch;
    }


    /**
     * 队列消息接收方法
     *
     * @param jsonMsg
     */
    public void receiveMessage(String jsonMsg) {
        log.info("[开始消费REDIS消息队列phone数据...]");
        try {
            System.out.println(jsonMsg);
            log.info("[消费REDIS消息队列phone数据成功.]");
        } catch (Exception e) {
            log.error("[消费REDIS消息队列phone数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }


    public void receiveMessageWang(String jsonMsg) {
        log.info("[开始消费REDIS消息队列phone数据...]");
        try {
            System.out.println(jsonMsg);
            log.info("[消费REDIS消息队列phone数据成功.]");
        } catch (Exception e) {
            log.error("[消费REDIS消息队列phone数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }

    /**
     * 队列消息接收方法
     *
     * @param jsonMsg
     */
    public void receiveMessage2(String jsonMsg) {
        log.info("[开始消费REDIS消息队列phoneTest2数据...]");
        try {
            System.out.println(jsonMsg);
            /**
             *  此处执行自己代码逻辑 操作数据库等
             */

            log.info("[消费REDIS消息队列phoneTest2数据成功.]");
        } catch (Exception e) {
            log.error("[消费REDIS消息队列phoneTest2数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }

}

6.代码测试

  public void redisTest() {
        //这个是测试同一个频道,不同的订阅者收到相同的信息,“phone”也就是topic也可以理解为频道
        redisTemplate.convertAndSend("phone", "223333");
        //这个phoneTest2是另外的一个频道
      //  redisTemplate.convertAndSend("phoneTest2", "34555665");
    }

二、Redis轮询机制

1.原理

   Redis的列表类型键可以用来实现队列,并且支持阻塞式读取,可以实现一个高性能的优先队列, 在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是4294967295。从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。  

2.生产者

  public R saveUserTicket(String phoneNum) {
        redisTemplate.opsForList().leftPush("ticket:Data", phoneNum);
        return R.ok();
    }

3.消费者

 @Scheduled(fixedRate = 1)
    public synchronized void consumer() {
            String message = redisTemplate.opsForList().rightPop("ticket:Data", 5, TimeUnit.SECONDS);
            if (!StringUtils.isEmpty(message)){
           //数据库操作
            }
    }

4.优化

如上述代码,如果此时队列为空,消费者依然会频繁拉取数据,造成CPU空转,不仅占用CPU资源还对Redis造成压力。因此当队列为空时我们可以休眠一段时间,再进行拉取。

实现如下

     @Scheduled(fixedRate = 1)
    public synchronized void consumer() throws InterruptedException {
        long a = redisTemplate.opsForList().size("ticket:Data");
        if (a == 0) {
            TimeUnit.SECONDS.sleep(1);//等待时间
        }
            String message = redisTemplate.opsForList().rightPop("ticket:Data", 5, TimeUnit.SECONDS);
            if (!StringUtils.isEmpty(message)){
           //数据库操作
            }
    }

Redis 设计商品秒杀 前言: 最近来了一个新需求,需要开发一个商品秒杀模块,而且需求很紧急须尽快上线。 方案1: 数据库锁进行控制,获取秒杀商品数量并加锁,如果数量大于零则成功,否则秒杀失败。 @Override @Transactional public Result startkilDBPCC_ONE(long seckillId, long userId) { ... 阅读详情

相关推荐

E-Prime脑电实验打mark全攻略:从端口查询到串并口实战(附常见问题解决)

本文提供了E-Prime脑电实验打mark的完整实战指南,涵盖端口查询、并口与串口配置的核心步骤及常见问题解决。文章深入解析了mark原理,并详细介绍了通过E-Basic脚本和Serial设备对象发送信号的两种方法,帮助研究者实现E-Prime与脑电设备的高精度时间同步,确保实验数据可靠性。

4k5l6j7h8的博客 354

Redis 消息订阅(MessageListener接口)

Redis 消息订阅

zhangjunli的博客 1万+

目标检测数据集 第069期-基于yolo标注格式的玻璃清洁状态检测数据集(含免费分享)

但这类技术的落地,离不开大量标注规范、场景丰富的数据集作为算法训练的支撑,玻璃清洁状态检测数据集的出现,正是为了满足这一市场与技术需求。在高校与科研机构的计算机视觉研究中,该数据集可作为基础研究素材,用于探索物体状态识别的算法优化方向,比如针对玻璃这类透明物体的检测算法改进,或是小样本场景下的状态识别研究。通过摄像头采集车窗、挡风玻璃的图像,算法自动检测玻璃清洁度,若识别到脏污影响视线,及时提醒运维人员清洁,避免因玻璃脏污引发的安全事故,提升交通出行的安全性。

深度学习算法工程师,专注于前沿算法研究与实战应用。在这里,我将分享深度学习领域的实战经验、优质数据集资源以及最新论文解读与精度分析,旨在与广大技术爱好者共同探索人工智能的奥秘,助力技术成长与创新。 127

springboot-redis-messageListener

主要实现两个功能1.过期事件监听 KeyspaceEventMessageListener2.发布订阅 RedisMeaasgeListener DispatchMessageListener MessagelistenerAdapter。

weixin_39523456的博客 3483

ThinkPHP 8.1 + think-swoole 4.1 实战:5分钟搞定WebSocket聊天室(附完整代码)

本文详细介绍了如何使用ThinkPHP 8.1和think-swoole 4.1快速构建高并发WebSocket聊天室。通过5分钟实战教程,开发者可以轻松实现用户列表、消息广播等核心功能,支持2000+并发连接,适用于客服系统、在线协作等实时通信场景。

ol789012345的博客 126

Android GNSS 模块分析(三)JNI 层

Android GNSS;定位服务;GPS;NMEA协议;LocationManagerService;导航;

Yang_Mao_Shan的博客 5155

Redis 发布订阅MessageListener

redis 发布订阅管理

qq_39042827的博客 1383

HP DL380G9 iLO降级Degrated及存储失败storage Failed报错如何解决?

HP DL380G9 iLO降级(Degrated)及存储失败(storage Failed)报错如何处理? ©Lander Zhang 专注外企按需IT基础架构运维服务,IT Helpdesk 实战培训践行者https://blog.51cto.com/lander 2018/09/7 7:30 问题描述 某沙特公司搭建的虚拟化环境中有一台ESXi主机报Storage Fialed警告但是仍...

weixin_34226182的博客 4847

如何使用Redis实现消息队列?

当前使用较多的有RabbitMQRocketMQActiveMQKafkaZeroMQMetaMQ等,而部分如RedisMySQL以及phxsql,如果硬搞的话,其实也可实现消息队列的功能。可能有人觉得,各种开源的 MQ 已经足够使用了,但是有些简单的业务场景,可能不需要重量级的 MQ 组件(相比 Redis 来说,Kafka 和 RabbitMQ 都算是重量级的消息队列)那如何使用 Redis 实现消息队列呢?接下来我们一起来看。

程序员高级码农的博客 2742

SVN使用:使用TortoiseSVN遇到提示请执行“清理”命令的解决方法

作为程序员来说,在团队开发过程中关于代码管理的工具使用并不陌生,比较常用的代码合并管理工具如:Git、SVN等,虽然相关工具的使用并不复杂,但是还是要掌握常用的使用方法,尤其是相关的第三方软件的使用也是极为重要的,而且有时候使用这些三方软件来进行代码管理的时候也会遇到一些问题。本篇博文就来分享一下在SVN使用的时候使用TortoiseSVN遇到提示请执行“清理”命令的解决方法,方便查阅使用。

全沾软贱开发攻城狮 1万+

高级数据库--作业及答案

中科大金培权老师的高级数据库课程作业及答案

一杯水果茶!足矣~ 1655

基于Redis实现原生的消息队列

虽然Redis 的Stream看似完美,但是它还有不足。1. 消息持久化依赖Redis持久化机制,知道Redis持久化的可以知道无法保证万无一失。2. 消息确认机制只支持消费者确认,不支持生产者确认。3. 不支持消息的事务机制4. 多消费者下的消息有序性问题。所以如果对于消息队列,有更严格的要求,建议使用专业的消息队列,比如RabbitMQ、RocketMQ、Kafka等。

2401_82540083的博客 1297

Dify框架面试内容整理-如何优化Dify的应用性能?

为1536维)可选用 PCA、FAISS 的量化压缩方法降低维度(如128、256、512);对知识库预览、任务流配置等页面,使用 nginx/cache-control 减少请求。API 响应耗时(P50/P95)、Token 使用量、缓存命中率、向量检索耗时;对 Prompt+输入内容计算 Hash,缓存相同请求的返回结果;使用占位符 + 函数拼接,按需组合 Prompt,而非静态长模板;数据量大时,应在构建索引前清洗冗余段落、统一格式,避免重复向量。维度越低,检索越快,但可能影响召回准确度,需平衡。

不务正业的猿 1548

Redis 实现消息队列

【Redis】介绍如何利用redis实现消息队列(超详细)

Mwt258的博客 6057

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Redis的消息队列是怎么实现的

List:适合简单的点对点队列。Pub/Sub:适合发布-订阅模式。Stream:适合高级消息队列,支持持久化、消费者组和消息确认。Sorted Set:适合延迟队列。根据具体需求选择合适的方式,可以充分发挥 Redis 的高性能和灵活性。

qq_36929638的博客 979

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

redis stream java消息队列_Redis-消息队列的两种实现方式

索引: 基于list的实现方式 基于publish/subscribe 实战消息队列简介消息队列:是消息的顺序集合。 比如网站的PV统计和查看,传统方式就是每个页面发一个AJAX然后mysql给PV+1。用户量非常大的时候,没有办法实时插入PV。 结合Redis消息队列的实现,也是每个用户访问的时候发送ajax到控制器,这个时候redis每次rpush pvlog,相当于直接往数组后面插入一万个行...

weixin_39873356的博客 824

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

redis实现消息队列

Stream 可以看作是一个由消息组成的日志,每个消息都有一个唯一的 ID(可以是时间戳或其他方式生成),并且可以对消息进行按照时间的顺序和优先级进行排序。它解耦了发送消息的应用程序和接收消息的应用程序之间的直接依赖关系,使得消息的发送者和接收者可以独立地演化和扩展。消息不能防止重复消费:Redis 的 pub/sub 模式不支持消息的确认和回调机制,因此,当订阅者收到消息时,无法对其进行确认,也就无法防止重复消费。相信在做分布式服务开发的时候,或多或少的使用到了消息队列,如主流的。

shigen的博客 1623

redis实现消息队列的几种方式

众所周知,redis是我们日常开发过程中使用最多的非关系型数据库,也是消息中间件。实际上除了常用的rabbitmq、rocketmq、kafka消息队列(大家自己下去研究吧~模式都是通用的),我们也能使用redis实现消息队列。因为其他中间件可能更适用于大型/企业级项目,在咱们项目前期不需要这么多的数据,redis跟我们也是高度集成的。这里就简化了技术栈。

qq_43419029的博客 5995

《基于Redis实现高效消息队列的完整指南》

Redis提供了多种实现消息队列的方案,各有特点:List结构适合简单FIFO队列,实现简单但功能有限;Pub/Sub模式支持实时广播,但消息不持久化;Stream是Redis 5.0引入的完整消息队列解决方案,支持消费者组、消息确认等企业级特性。生产环境中,应根据消息重要性、吞吐量等需求选择合适方案,关键业务建议使用Stream并配合监控机制。这三种方式各有优劣,开发者在选择时需权衡功能需求与实现复杂度。

yh云想的博客 1350

Redis如何实现消息队列

Redis 利用 其 专有的数据结构list可以实现简单的消息队列和异步发送的需求,当然可专业的mq相比,在消息投递的可靠性和及时性上还是有差距,如果对异步发送要求比较高,则建议还是采用专业的mq中间件。

藏经阁 5747

redis之如何实现消息队列

消息队列,顾名思义,就是一个存放消息的队列。最简单的消息队列包含3个角色生产者:将消息存入队列中 队列:存放和管理消息 消费者: 将消息从队列中取出来并做业务处理R e d i s 提供了三种实现消息队列的方式,基于 L i s t 结构、 P u b S u b 、 S t r e a m 结构 \textcolor{r...

weixin_29013129的博客 506
上一篇: Spring Boot配置Redis
下一篇: Redis语法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值