
导语学习 Redis 时很多人都会有一个疑问。Redis 已经提供了List和Pub/Sub发布/订阅它们都能够实现消息队列为什么 Redis 5.0 又新增了一种Stream数据类型它是为了提升性能吗还是为了替代 List答案都不是。Stream 的出现并不是因为原来的数据类型不能发送消息而是因为它们在生产环境中存在一些难以解决的问题。当业务越来越复杂时仅仅能够发送和接收消息已经远远不够消息是否会丢失、是否能够重复消费、是否能够查看历史记录等问题才是真正影响系统可靠性的关键。理解了这些问题再去学习 Stream 的各种命令就会更容易理解它的设计思路而不是停留在记忆命令的层面。一、什么是 StreamStream消息流是 Redis 5.0 引入的一种数据类型主要用于消息队列场景。它按照消息产生的先后顺序保存数据每条消息都会拥有一个唯一的Message ID方便后续读取、查询和管理。下面是一条 Stream 中保存的三条订单消息订单消息 -------------------------------------------------- | Message ID | 消息内容 | -------------------------------------------------- | 1750000000000-0 | userTom amount100 | | 1750000000100-0 | userJack amount250 | | 1750000000250-0 | userAlice amount80 | --------------------------------------------------每当有新的消息产生时Redis 都会将它追加到 Stream 的末尾并自动生成一个唯一的 Message ID。随着消息不断增加整个数据结构就像一条持续流动的河流因此 Redis 官方将它命名为Stream流。与 List 不同消息被读取后不会立即删除而是可以继续保存在 Stream 中因此既能够读取最新消息也能够查询历史消息。二、为什么会有 Stream在 Redis 5.0 之前实现消息队列通常有两种方案ListPub/Sub它们都能够完成消息的发送与接收因此很多人都会认为已经足够使用。实际上在一些简单场景下它们确实能够满足需求。但随着业务越来越复杂对消息队列提出了更高的要求消息不能轻易丢失。一个消息能够被多个消费者处理。消费失败后能够重新处理。能够知道消息是否已经处理完成。能够查看历史消息。这些能力List 和 Pub/Sub 都无法同时满足。也正因为如此Redis 在 5.0 中推出了 Stream为消息队列提供了一套更加完善的解决方案。三、List 和 Pub/Sub 存在哪些问题很多人第一次接触 Stream 时都会问既然 Redis 已经能够实现消息队列为什么还要新增一种数据类型答案就在于它们各自的局限性。ListList 是最早用于实现消息队列的数据类型。通常通过下面几个命令完成消息的生产和消费LPUSH / RPUSH生产消息LPOP / RPOP消费消息BLPOP / BRPOP阻塞等待消息对于简单任务队列来说List 已经足够使用。但是它存在几个明显的问题消息被消费后立即删除。无法查看历史消息。一个消息只能被一个消费者消费。无法确认消息是否真正处理成功。消费过程中如果程序崩溃消息可能永久丢失。假设一个订单系统中用户支付成功后需要完成下面几个操作扣减库存创建订单通知仓库发货增加会员积分如果消费者刚从 List 中取出消息程序突然崩溃由于消息已经被删除那么后续所有操作都会失败并且这条消息无法重新恢复。对于支付、订单等业务来说这是无法接受的。Pub/SubPub/Sub 更像一种广播机制。发布者 │ ▼ Redis ┌────┼────┐ ▼ ▼ ▼ 用户A 用户B 用户C发布者发送一条消息后所有订阅该频道的客户端都会立即收到。它非常适合聊天室实时通知消息广播在线推送但是它同样存在不足消息不会保存。用户离线期间发送的消息会直接丢失。无法查看历史消息。无法确认消息是否被成功消费。因此Pub/Sub 更适合实时通信而不是需要保证消息可靠性的业务。四、Stream 解决了哪些问题Stream 的设计目标就是解决传统消息队列方案存在的问题。相比 List 和 Pub/Sub它增加了很多重要能力消息默认保存不会因为读取而立即删除。每条消息都有唯一的 Message ID。支持多个消费者共同消费同一个消息流。支持消费者组Consumer Group。支持消息确认ACK。消费失败后可以重新处理消息。可以查看历史消息。支持阻塞读取提高消费效率。这些能力共同组成了一套更加完整的消息队列机制也让 Redis 能够胜任更多生产环境中的消息处理场景。五、Stream 与 List、Pub/Sub 有什么区别把三种方案放在一起就能更容易理解 Stream 为什么会出现。对比项ListPub/SubStream可以实现消息队列✔✔✔消息是否保存✔✘✔支持历史消息✘✘✔一个消息支持多个消费者✘✔✔支持消费者组✘✘✔支持消息确认ACK✘✘✔支持失败重试✘✘✔支持阻塞读取✔✔✔消息可靠性一般较低较高推荐场景简单任务队列实时广播可靠消息队列从这张表可以看出Stream 并不是为了替代 List 或 Pub/Sub。如果只是实现一个简单队列List 更简单。如果只是广播消息Pub/Sub 更方便。而当业务开始关注消息可靠性时Stream 才是更合适的选择。六、什么时候适合使用 Stream下面这些场景都比较适合使用 Stream订单处理支付系统秒杀系统日志收集消息通知异步任务数据同步这些业务都有一个共同特点消息不能轻易丢失。通过消费者组、消息确认、失败重试等机制Stream 能够保证消息被可靠地处理提高整个系统的稳定性。当然Stream 也并不是所有消息场景的最佳选择。如果只是聊天室、实时广播等即时通信业务Pub/Sub 往往更加简单。如果只是实现一个轻量级任务队列List 也已经能够满足需求。只有当业务对消息可靠性提出更高要求时Stream 的优势才会真正体现出来。总结Redis 的每一种数据类型都有明确的设计目标。String 用于键值存储Hash 用于存储对象List 擅长处理顺序数据Set 和 Sorted SetZSet分别解决了去重与排序问题。而 Stream 的出现则是为了满足消息队列对可靠性的需求。随着业务越来越复杂仅仅能够发送和接收消息已经远远不够。开发者开始希望消息能够保存、能够确认是否消费成功、能够支持多个消费者协作、能够在消费失败后重新处理。这些能力都是 List 和 Pub/Sub 难以同时满足的。因此Stream 并不是为了替代它们而是在保留 Redis 高性能、轻量级特点的基础上为消息队列提供了一套更加完善的解决方案。学习 Stream 时与其先记忆大量命令不如先理解它为什么会出现、解决了哪些问题。当理解了它的设计思想之后再去学习各个命令时就会发现它们之间并不是彼此独立而是共同组成了一套完整的消息队列机制。