day41 Redis三种消息队列与Stream实现异步秒杀

1. 今天做了什么

  • 学习 Redis 消息队列的三种实现方式:基于 List、基于 PubSub、基于 Stream
  • 学习 Stream 消息队列的 XREAD 命令及其优缺点
  • 学习 Stream 消费者组的概念、命令和解决的问题
  • 基于 Redis Stream 消息队列实现异步秒杀下单

2. 核心知识点

消息队列基础

  • 消息队列(Message Queue):存储和管理消息,也被称为消息代理
  • 为什么引入消息队列:
    • 异步响应,提高速度
    • 高峰时期大量请求先加入消息队列,后端按自己的处理能力匀速消耗
  • 消息队列的基本要求:
    1. JVM 以外,不受 JVM 内存限制
    2. 对数据进行持久化,保证数据安全
    3. 保证消息有序性
    4. 消费者需要对消息进行确认,确保消息至少被消费一次
  • 主流中间件有 RabbitMQ 等,这里使用 Redis 实现(3 种方式,了解即可)

方式一:基于 List 模拟

  • 利用 BLPOP、BRPOP 来取数据,才具有阻塞效果
  • 优点:基本满足前 3 条(存储在 Redis,可以持久化,list 是有序的)
  • 缺点:
    • 无法避免消息丢失,不满足第 4 条
    • 只支持单消费者

方式二:基于 PubSub(发布订阅)

  • Redis 发布订阅命令:
    1. PUBLISH channel message:将信息发送到指定的频道
    2. PSUBSCRIBE pattern [pattern ...]:订阅一个或多个符合给定模式的频道
    3. SUBSCRIBE channel [channel ...]:订阅给定的一个或多个频道的信息
  • 优点:采用发布订阅,支持多生产多消费者
  • 缺点:无法保证数据安全
    • 不支持持久化,无人订阅则丢失
    • 无法避免消息丢失
    • 消费者的消息堆积有上限,超出则丢失

方式三:基于 Stream 消息队列

  • 发送与接收的命令(见截图)

发送与接收方法

XREAD命令

  • XREAD 命令读取优缺点:
    • 消息永久保存,可回溯
    • 可阻塞读取
    • 有漏读问题($ 读取最新消息,如果处理一个消息过程中有消息到来则会漏读)

Stream 消费者组

  • 消费者组可以解决 XREAD 漏读的问题

创建消费者组:

1
2
3
4
5
6
XGROUP CREATE key groupName ID [MKSTREAM]

- key: 队列名称
- groupName: 消费者组名称
- ID: 起始ID标示,$代表队列中最后一个消息,0则代表队列中第一个消息
- MKSTREAM: 队列不存在时自动创建队列

其它常见命令:

1
2
3
4
5
6
7
8
# 删除指定的消费者组
XGROUP DESTORY key groupName

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

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

从消费者组读取消息:

1
2
3
4
5
6
7
8
9
10
11
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]

- group: 消费组名称
- consumer: 消费者名称,如果消费者不存在,会自动创建一个消费者
- count: 本次查询的最大数量
- BLOCK milliseconds: 当没有消息时最长等待时间
- NOACK: 无需手动ACK,获取到消息后自动确认
- STREAMS key: 指定队列名称
- ID: 获取消息的起始ID:
- ">": 从下一个未消费的消息开始
- 其它: 根据指定id从pending-list中获取已消费但未确认的消息,例如0,是从pending-list中的第一个消息开始

消费者组解决了:

  1. 漏读问题,可以设置 “>” 从下一个未消费的消息开始
  2. 确认机制,未确认的消息保存在 pending-list,确保每个消息都被确认,防止消费者处理时异常导致消息丢失
  3. 一个消费者组中的消费者消费同一队列,可以提高效率
  4. 设置多个消费者组,可以实现多消费

实际选型建议

  1. 在实际开发中,list 与 PubSub 实现的消息队列缺点过多,不考虑
  2. 简单业务需求可以使用 Redis 的 Stream 消息队列
  3. 复杂场景,有复杂需求,还是要使用专业的中间件,如 RabbitMQ

3. 深度思考

4. 踩过的坑

5. 遗留问题 / 想不通