跳转至

Redis Stream 1 概念

概要: 简介Redis Stream的来源和优缺点

创建时间: 2022.10.21 00:42:02

更新时间: 2023.08.16 22:25:42

Redis Stream背景

在Redis 5.0引入Stream之前,通常使用Redis的pubsub作为消息队列,但其存在消息丢失和服务不稳定的问题,于是才有了后来的Redis Stream结构。

Stream特性与改进

稳定的消息服务必须至少具备以下两个特点

  1. 保证信息不会丢失,至少被消费一次
  2. 要具备削峰填谷的能力,能够匹配生产者和消费者的吞吐差异

根据上面的论述,Redis Stream作出的改进有如下方面

  1. 存储消息数据时Redis使用紧凑型的listpack结构,这样能够提高Stream数据的空间利用率。
  2. Stream引入了消费者群组的概念,每个群组中可以包含多个消费者。群组中的所有消费者共享一个消息位点,即 last_delivered_id ,从而能够在一个群组内加入多个消费者来提升吞吐量。对于一个消费者群组,每条消息只可以被其中一个消费者获取和处理。
  3. Stream中不同的消费者群组之间相互隔离,每个群组维护自己的 last_delivered_id ,这样可以实现一条消息被多个不同的消费者群组消费,即实现了消息广播的能力。
  4. Stream中消费者获取消息的方式是拉取,也能够设置一定的阻塞时长,在没有消息的时候阻塞,这种长轮询机制保证了消息的实时性,且消费速率和消费者自身吞吐能力相匹配。

Stream如何保证不丢失消息

  1. Stream的存储在 aofrdb 文件中,这些硬盘持久化的数据可以保证Redis重启后能够恢复Stream的数据。此功能是之前pubsub类型数据不具备的。
  2. Stream中每个消费者群组的 last_delivered_id 标识着当前群组读取到的消息的位置,如果客户端断链,重连后依然能从此位点读取,消息不会丢失。
  3. Stream还引入了 ack 机制,从消费者端来确保消息已被成功消费掉。对于消息已被读取但没有 ack 的场景,Stream会标识此条消息为 pending 的状态,等到客户端重连后,可以通过 xpending 的命令获取这些 pending 状态的消息,继续处理。即使当前应用永久离线,那么同群组的其他消费者也能够读取到这条消息,并通过 xclaim 命令进行消费和处理。

image.png

Stream消息重复消费问题

上面提到Stream能够消息不丢失,进而被消费,那么如何保证不被重复消费呢?

有两种重复消费的场景需要分别分析:

  1. 生产者重复投递消息。Stream要求每条消息必须有唯一的递增的ID,可以通过引入ID参数 * 来让Redis自动产生ID,也可以应用自己生成ID。如果生产者 xadd 超时,那么可以通过 xread 此 ID 来查询此消息是否存在,存在就说明已经成功投递,否则就需要重新投递。另一方面,Stream要求ID的递增性,则意味着已经存在的消息再次重复投递将被拒绝。Redis Stream的上述机制保证了每条消息仅能被投递一次。
  2. 消费者重复消费消息。如果消费者确实读取到了消息,也已经处理完了业务,但没来得及 ack 就异常离线,那么此应用重启后将会重复消费此条消息。此问题根源在于 ack 和消费者的业务系统是相互独立的,这种场景只能在业务端进行防护和改造

Stream的不足

  1. 削峰填谷功能。在Stream中,如果生产者投递速度过快过多,而消费者消费的速度远远没消息投递速度快,这样就会造成消息积压,且随着时间持续会愈发严重。由于Redis基于内存,消息数据造成的数据堆积成本要远高于硬盘。
  2. 可能丢失消息。在Stream中,虽然 ack 机制能够保证消息能被至少消费一次,但前提是这条消息已经持久化到了硬盘中。Redis数据持久化以来 aofrdb 文件,aof 落盘一般为 everysec 即每秒一次,而 rdb 文件则是在全量备份时生成,这意味着,如果Redis突然宕机可能会丢掉最近一秒的数据。另一方面,如果线上的Redis为多节点高可用架构,当主Redis宕机后,如果切换到备Redis节点继续提供服务,那么没有从主Redis同步的数据将会全部丢失。也就是说,如果Redis出现故障,就不能保证消息被至少消费一次。

Stream总结

image.png
备注:背压机制(Backpressure)描述的是这样一种现象。在数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出,这种现象就叫做 Backpressure 出现。

参考