
一、为什么实现消息队列消息队列的核心作用是把“生产消息”和“处理消息”分开。例如订单业务用户下单 ↓ 生产者把订单任务放入 Redis ↓ 消费者从 Redis 中取出订单任务 ↓ 异步创建数据库订单这样可以实现削峰高并发请求先进入 Redis数据库慢慢处理。异步用户不必等待全部业务执行完毕。解耦生产者只负责发送任务消费者只负责处理任务。多实例共享多个应用可以使用同一个 Redis 队列。Redis 中常见的消息通信方式包括List竞争消费适合简单任务队列。 Pub/Sub广播消费适合实时通知。 Stream更可靠适合订单、秒杀等重要业务。二、Redis List 实现消息队列1. List 的基本思想Redis List 是一个有顺序的双端列表可以从左边或右边放入、取出元素。最常见的队列组合生产者RPUSH 消费者BLPOP逻辑如下左边 右边 ← BLPOP [消息1消息2消息3] RPUSH →生产者从右边加入消息消费者从左边取消息因此满足先进先出。先进入的消息通常先被处理。这就是 FIFOFirst In First Out先进先出。2. List 常用命令命令作用LPUSH key value从左边加入元素RPUSH key value从右边加入元素LPOP key从左边取出并删除元素RPOP key从右边取出并删除元素BLPOP key timeout从左边阻塞获取元素BRPOP key timeout从右边阻塞获取元素LRANGE key 0 -1查看 List 全部内容DEL key删除 List3. 最简单的 List 队列假设订单队列名称为order.queue生产者发送订单消息RPUSH order.queue order-1001 RPUSH order.queue order-1002 RPUSH order.queue order-1003此时逻辑结构为左边 右边 [ order-1001 , order-1002 , order-1003 ]查看全部消息LRANGE order.queue 0 -1结果类似1) order-1001 2) order-1002 3) order-1003消费者取出一条消息LPOP order.queue得到order-1001此时队列变成[ order-1002 , order-1003 ]注意LPOP 不只是读取消息而是取出并删除消息。4. 为什么实际消费通常用 BLPOP如果队列为空LPOP order.queue会返回(nil)如果消费者一直循环执行LPOPLPOP 没有消息 LPOP 没有消息 LPOP 没有消息这叫轮询会不断访问 Redis浪费 CPU 和 Redis 资源。因此实际消费者通常使用阻塞命令BLPOP order.queue 0含义如果队列有消息立即取出一条。 如果队列为空当前客户端阻塞等待。 0 表示永不超时一直等待。它类似 Java 中orderTasks.take();对应关系Java BlockingQueueRedis Listput(message)RPUSH queue messagetake()BLPOP queue 0poll()LPOP queue5. 多消费者竞争消费可以开多个终端分别作为不同消费者。终端 A生产者 终端 B消费者 c1 终端 C消费者 c2消费者 c1BLPOP order.queue 0消费者 c2BLPOP order.queue 0生产者发送消息RPUSH order.queue order-1001Redis 会把这条消息交给某一个正在等待的消费者。c1 或 c2 中只有一个能拿到 order-1001。另一个消费者不会拿到同一条消息。这叫竞争消费。多个消费者共同竞争队列中的消息提高处理能力。注意BLPOP 一次只取一条消息。 拿到消息后命令结束。 想继续消费需要再次执行 BLPOP。未来在 Java 中会放进循环while (true) { // 阻塞获取消息 // 处理消息 }6. List 不存在时是否自动创建生产者第一次执行RPUSH order.queue order-1001如果order.queue不存在Redis 会自动创建一个 List再写入消息。因此不需要提前创建队列。不同命令的行为操作队列不存在时的行为RPUSH/LPUSH自动创建 List 并写入消息LPOP/RPOP返回(nil)不会创建BLPOP/BRPOP阻塞等待不会创建取走最后一条消息后空 List 通常自动删除例如消费者先执行BLPOP order.queue 0即使order.queue还不存在也会阻塞等待。之后生产者执行RPUSH order.queue order-1001Redis 会自动创建 List并立刻将消息交给等待中的消费者。7. List 的可靠性问题普通 List 队列的消费过程队列中有消息 ↓ 消费者 BLPOP / LPOP ↓ 消息立即从 List 删除 ↓ 消费者开始处理业务如果消费者取到消息后立刻宕机消费者拿到 order-1001 ↓ Redis 已删除 order-1001 ↓ 消费者还没写入 MySQL 就宕机 ↓ 消息丢失因此RPUSH BLPOP 简单但不能可靠保证消息一定被处理完成。它适合日志、简单通知、可容忍少量丢失的异步任务。不适合订单创建、支付、库存扣减、秒杀订单等重要业务。8. List 的改进处理中队列可以准备两个 Listorder.queue 待处理队列 order.processing 正在处理队列生产者发送消息RPUSH order.queue order-1001消费者不直接删除消息而是使用BRPOPLPUSH order.queue order.processing 0含义从 order.queue 右边取出一条消息 放到 order.processing 左边 整个移动过程是原子的。流程待处理队列 ↓ 原子移动 ↓ 处理中队列 ↓ 业务处理成功 ↓ 从处理中队列删除消息处理成功后LREM order.processing 1 order-1001如果消费者宕机消息仍保留在 order.processing 中 不会立即消失。但这种方案的重试、超时检测、重复消费控制、失败记录等逻辑都要自己实现。因此 List 可以改进可靠性但实现复杂度会逐渐接近 Stream。三、Redis Pub/Sub 发布订阅1. Pub/Sub 的基本思想Pub/Sub 是 Publish / Subscribe 的缩写即发布 / 订阅。它不是传统“一个消费者取走一条消息”的队列而是广播机制。发布者 Publisher ↓ PUBLISH 频道 Channel ↓ SUBSCRIBE 多个订阅者 Subscriber例如发布者 ↓ news ├── 订阅者 A 收到 ├── 订阅者 B 收到 └── 订阅者 C 收到一条消息会被所有当前在线、订阅该频道的客户端收到。2. Pub/Sub 核心命令命令作用PUBLISH channel message向频道发布消息SUBSCRIBE channel精确订阅频道UNSUBSCRIBE channel取消精确订阅PSUBSCRIBE pattern按模式订阅频道PUNSUBSCRIBE pattern取消模式订阅3. 基础发布与订阅终端 A 作为订阅者SUBSCRIBE news表示订阅频道news终端 B 作为发布者PUBLISH news hello redis返回(integer) 1表示当前有 1 个订阅者收到了消息。终端 A 会收到1) message 2) news 3) hello redis含义message 表示收到普通频道消息 news 消息所属频道 hello redis 消息内容4. Pub/Sub 的广播特性假设两个订阅者都执行SUBSCRIBE news然后发布者发送PUBLISH news 秒杀活动开始两个订阅者都会收到秒杀活动开始这和 List 不同。List一条消息通常只会被一个消费者取走。Pub/Sub一条消息会广播给所有在线订阅者。5. Pub/Sub 为什么不适合订单消息Pub/Sub 不保存历史消息。假设没有订阅者时发布PUBLISH news 活动开始返回(integer) 0表示当前没有在线订阅者。这条消息不会保存。之后即使有人执行SUBSCRIBE news也收不到之前那条消息。因此 Pub/Sub 的特点是先订阅才能收到之后发布的消息。如果订阅者断线、宕机、网络异常断线期间的消息会直接错过。Pub/Sub 没有消息持久化 确认机制 Pending List 失败重试 消费者组所以它不适合订单 支付 库存扣减 秒杀下单这些业务必须保证消息可靠处理。6. Pub/Sub 适合的场景Pub/Sub 适合“所有在线客户端都应该立刻知道”的通知型场景。例如聊天室消息广播 系统维护通知 缓存失效通知 在线用户状态变化 实时监控大屏更新 WebSocket 推送辅助通知可以把 Pub/Sub 理解为微信群公告。在线的人能立刻看到不在线的人会错过。7. 订阅多个频道可以一次订阅多个频道SUBSCRIBE news system.notice cache.clear之后只要其中任意频道有消息当前客户端都能收到。例如PUBLISH news 新闻更新 PUBLISH system.notice 服务器十分钟后维护 PUBLISH cache.clear 请清理商品缓存8. 取消普通订阅取消某个频道UNSUBSCRIBE news取消当前客户端全部普通订阅UNSUBSCRIBE对应关系SUBSCRIBE 订阅精确频道 UNSUBSCRIBE 取消精确频道订阅9. 模式订阅PSUBSCRIBE除了精确订阅频道还可以按模式订阅。例如PSUBSCRIBE news.*表示订阅所有以news.开头的频道。可以匹配news.sports news.tech news.game news.local发布者执行PUBLISH news.sports 比赛开始或者PUBLISH news.tech Redis 通知模式订阅者都会收到。模式订阅收到的格式类似1) pmessage 2) news.* 3) news.sports 4) 比赛开始含义pmessage 表示模式订阅消息 news.* 当前订阅模式 news.sports 实际发布频道 比赛开始 消息内容10. PUNSUBSCRIBE 的作用PUNSUBSCRIBE是取消模式订阅。例如之前执行PSUBSCRIBE news.*现在取消PUNSUBSCRIBE news.*取消全部模式订阅PUNSUBSCRIBE对应关系订阅方式取消方式SUBSCRIBE newsUNSUBSCRIBE newsPSUBSCRIBE news.*PUNSUBSCRIBE news.*因此PUNSUBSCRIBE 的含义是 Pattern Unsubscribe 即取消模式订阅。11. 普通订阅和模式订阅重叠的问题假设同一个客户端既执行SUBSCRIBE news.sports又执行PSUBSCRIBE news.*之后发布PUBLISH news.sports 比赛开始该客户端可能收到两次消息一次来自精准订阅 news.sports 一次来自模式订阅 news.*因此开发时应避免同一个业务对同一频道建立重叠订阅否则可能重复处理通知。四、List 与 Pub/Sub 对比对比项Redis ListRedis Pub/Sub核心模式任务队列消息广播一条消息由谁处理一个消费者所有在线订阅者消息是否保存暂时保存在 List 中不保存消费者离线时消息可暂存在队列直接错过消息是否支持阻塞等待支持BLPOP/BRPOP订阅后持续接收是否有确认机制没有没有是否容易丢消息消费后宕机可能丢离线或异常时可能丢典型用途简单异步任务实时广播通知是否适合订单不够可靠不适合五、最终记忆List 队列最常见写法RPUSH order.queue 消息 BLPOP order.queue 0含义生产者从右边放消息 消费者从左边阻塞取消息 实现先进先出的竞争消费。优点简单 支持阻塞 多个消费者可竞争消费缺点消息取出后立即删除 消费者宕机可能导致消息丢失 没有确认、重试、消费者组机制Pub/Sub最常见写法SUBSCRIBE news PUBLISH news 消息含义发布者向频道广播消息 所有当前在线订阅者都会收到。模式订阅PSUBSCRIBE news.* PUNSUBSCRIBE news.*优点实时广播 使用简单 适合通知类场景缺点不保存消息 订阅者离线就会丢消息 没有确认和重试 不适合订单等重要业务一句话区分List谁抢到谁处理。 Pub/Sub谁订阅谁都收到。 Stream可靠地分配、确认、恢复消息。