黑马点评实战篇|第七篇:Redis消息队列

目录

1.Redis消息队列-认识消息队列(MQ)

2.Redis消息队列-基于List实现消息队列

3.Redis消息队列-基于PubSub的消息队列

4.Redis消息队列-基于Stream的消息队列

5.Redis消息队列-基于Stream的消息队列-消费者组

6.基于Redis的Stream结构作为消息队列,实现异步秒杀下单


1.Redis消息队列-认识消息队列(MQ)

消息队列的定义:是基于 Redis 实现的消息异步传输、暂存与消费的中间件方案,主要用于存放消息,管理消息

消息队列的基础模型包含三个部分:

  • 消息队列:存储和管理消息,也被称为消息代理(Message Broker)

  • 生产者:发送消息到消息队列

  • 消费者:从消息队列获取消息并处理消息

消息队列的好处:让消息生产者 (发送消息的服务)和消息消费者 (处理消息的服务)解耦,生产者只管发消息,消费者只管取消息处理,无需同时在线、无需直接通信,可以异步进行操作

举例:快递员(生产者)把快递放到快递柜里边(Message Queue)去,我们(消费者)从快递柜里边去拿东西,这就是一个异步,如果耦合,那么这个快递员相当于直接把快递交给你,这事固然好,但是万一你不在家,那么快递员就会一直等你,这就浪费了快递员的时间,所以这种思想在我们日常开发中,是非常有必要的。

redis中有三种实现消息队列的方式:

1.list队列

2.PubSub发布订阅

3.Stream(常用)

2.Redis消息队列-基于List实现消息队列

因为队列需要出口和入口不在同一边,所以可以用list的LPUSH 和 BRPOP来从左端口进右端口出,来模拟消息队列的效果

从redis-cli控制台使用help @list中也可以看到每一个的效果

注意BRPOP是需要控制阻塞时间

基于List的消息队列有哪些优缺点?

优点:

  • 利用Redis存储,不受限于JVM内存上限

  • 基于Redis的持久化机制,数据安全性有保证

  • 可以满足消息有序性

缺点:

  • 无法避免消息丢失

  • 只支持单消费者

3.Redis消息队列-基于PubSub的消息队列

PubSub(发布订阅)是Redis2.0版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个channel,生产者向对应channel(频道)发送消息后,所有订阅者都能收到相关消息。

SUBSCRIBE channel [channel] :订阅一个或多个频道

PUBLISH channel msg :向一个频道发送消息

PSUBSCRIBE pattern[pattern] :订阅与pattern格式匹配的所有频道

只要执行了SUBSCRIBE命令订阅频道就会开始监听这个频道,直到这个频道发布消息

但是有可能会出现消息丢失,如果在给频道发送消息时,没有监听就会丢失消息

下面返回0就是没有消费者监听频道,消息就丢失了,因为这个消息不会存储在redis里,所以不能像list一样持久化

基于PubSub的消息队列有哪些优缺点?

优点:

  • 采用发布订阅模型,支持多生产、多消费

缺点:

  • 不支持数据持久化

  • 无法避免消息丢失

  • 消息堆积有上限,超出时数据丢失

4.Redis消息队列-基于Stream的消息队列

Stream 是 Redis 5.0 引入的一种新数据类型,可以实现一个功能非常完善的消息队列。

因为是一种数据类型,所以可以做到数据持久化

写命令和读命令

xadd

xread

注意如果需要写count参数一定要写count,block同理

但如果我们一次性发送多条消息,只读最新的一条的话可能会漏读消息

如图,当我们往stream里添加了4个消息时,只读到了一个

在业务开发中,我们可以循环的调用XREAD阻塞方式来查询最新消息,从而实现持续监听队列的效果,伪代码如下

5.Redis消息队列-基于Stream的消息队列-消费者组

消费者组(Consumer Group):将多个消费者划分到一个组中,监听同一个队列。具备下列特点:

创建消费组组:XGROUP CREATE key groupname id|$ [MKSTREAM]

key:队列名称

groupName:消费者组名称ID:起始ID标示,$代表队列中最后一个消息,0则代表队列中第一个消息

MKSTREAM:队列不存在时自动创建队列

删除指定的消费者组 :XGROUP DESTORY key groupName

给指定的消费者组添加消费者 ​​​​​​​:XGROUP CREATECONSUMER key groupname consumername

删除消费者组中的指定消费者 ​​​​​​​:XGROUP DELCONSUMER key groupname consumername

从消费者组读取消息: XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]

在xread的基础上多了消费者组

  • group:消费组名称

  • consumer:消费者名称,如果消费者不存在,会自动创建一个消费者

  • count:本次查询的最大数量

  • BLOCK milliseconds:当没有消息时最长等待时间

  • NOACK:无需手动ACK,获取到消息后自动确认,必须手动写 NOACK,才会自动确认!

  • STREAMS key:指定队列名称

  • ID:获取消息的起始ID:

  • ">":从下一个未消费的消息开始其它:根据指定id从pending-list中获取已消费但未确认的消息,例如0,是从pending-list中的第一个消息开始

消费者监听消息的基本思路:

先监听队列,如果为null说明没有消息则继续下一次,有消息就处理消息并且给消息ACK,如果没有成功处理消息就会抛出异常进入pending List队列,然后再catch中读取消费者的pending List把> 改成 0,即把从读最新消息改成第一个没有ACK的,因为此时是已经读过但没处理所以需要改成0,再次处理,直到ACK
Pending List中的一定是没有ACK的消息但不一定是没有处理的消息

6.基于Redis的Stream结构作为消息队列,实现异步秒杀下单

需求:

  • 创建一个Stream类型的消息队列,名为stream.orders

  • 修改之前的秒杀下单Lua脚本,在认定有抢购资格后,直接向stream.orders中添加消息,内容包含voucherId、userId、orderId

  • 项目启动时,开启一个线程任务,尝试获取stream.orders中的消息,完成下单

java 复制代码
private class VoucherOrderHandler implements Runnable {

    @Override
    public void run() {
        while (true) {
            try {
                // 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
                List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                    Consumer.from("g1", "c1"),
                    StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                    StreamOffset.create("stream.orders", ReadOffset.lastConsumed())
                );
                // 2.判断订单信息是否为空
                if (list == null || list.isEmpty()) {
                    // 如果为null,说明没有消息,继续下一次循环
                    continue;
                }
                // 解析数据
                MapRecord<String, Object, Object> record = list.get(0);
                Map<Object, Object> value = record.getValue();
                VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                // 3.创建订单
                createVoucherOrder(voucherOrder);
                // 4.确认消息 XACK
                stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());
            } catch (Exception e) {
                log.error("处理订单异常", e);
                //处理异常消息
                handlePendingList();
            }
        }
    }

    private void handlePendingList() {
        while (true) {
            try {
                // 1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0
                List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                    Consumer.from("g1", "c1"),
                    StreamReadOptions.empty().count(1),
                    StreamOffset.create("stream.orders", ReadOffset.from("0"))
                );
                // 2.判断订单信息是否为空
                if (list == null || list.isEmpty()) {
                    // 如果为null,说明没有异常消息,结束循环
                    break;
                }
                // 解析数据
                MapRecord<String, Object, Object> record = list.get(0);
                Map<Object, Object> value = record.getValue();
                VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                // 3.创建订单
                createVoucherOrder(voucherOrder);
                // 4.确认消息 XACK
                stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());
            } catch (Exception e) {
                log.error("处理pendding订单异常", e);
                try{
                    Thread.sleep(20);
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}
相关推荐
杨云龙UP2 小时前
Oracle / ODA环境TRACE、alert日志定位与ADRCI清理 SOP_20260423
linux·运维·服务器·数据库·oracle
科技小花2 小时前
测评|2026五大数据治理平台横向对比:谁在定义数据中台的“智能引擎”?
大数据·数据库·人工智能·数据治理·数据中台
千月落2 小时前
Redis Cluster 集群部署
数据库·redis·缓存
撩得Android一次心动2 小时前
Android Room 数据库详解【使用篇】
android·数据库·room·jetpack
yaoxin5211232 小时前
388. Java IO API - 处理事件
java·服务器·数据库
colofullove2 小时前
推导中异常处理
数据库·oracle
卷毛的技术笔记2 小时前
从零到一:深入浅出分布式锁原理与Spring Boot实战(Redis + ZooKeeper)
java·spring boot·redis·分布式·后端·面试·java-zookeeper
黑牛儿2 小时前
MySQL主流存储引擎深度解析:优缺点对比+实操选型指南
数据库·mysql
奋斗tree2 小时前
SQL Server数据库自动备份终极指南方法三
数据库·oracle