第 9 章 流

流(stream)是 Redis 5.0 版本中新增加的数据结构,也是该版本最重要的更新。在以往的版本中,为了实现消息队列这一常见应用,用户往往会使用列表、有序集合和发布与订阅这 3 种功能,但这些不同的实现都有各自的缺陷:

  • 列表实现的消息队列虽然可以快速地将新消息追加到列表的末尾,但因为列表为线性结构,所以程序如果想要查找包含指定数据的元素,或者进行范围查找,就需要遍历整个列表。

  • 有序集合虽然可以有效地进行范围查找,但缺少列表和发布与订阅提供的阻塞弹出原语,这使得程序无法使用有序集合去实现可阻塞的消息弹出操作。

  • 发布与订阅虽然拥有将消息传递给多个客户端的能力,并且也拥有相应的阻塞弹出原语,但发布与订阅的 “发送即忘(fire and forget)” 策略会导致离线的客户端丢失消息,所以它是无法实现可靠的消息队列的。

除了以上 3 种数据结构各自具有的问题之外,还有一个问题是 3 种数据结构共有的:无论是列表、有序集合还是发布与订阅,它们的元素都只能是单个值。换句话说,如果用户想要用这些数据结构实现的消息队列传递多项信息,那么必须使用 JSON 之类的序列化格式来将多项信息打包存储到单个元素中,然后再在取出元素之后进行相应的反序列化操作。

Redis 流的出现解决了上述提到的所有问题,它是上述 3 种数据结构的综合体,具备它们各自的所有优点以及特点,是使用 Redis 实现消息队列应用的最佳选择。流是一个包含零个或任意多个流元素的有序队列,队列中的每个元素都包含一个 ID 和任意多个键值对,这些元素会根据 ID 的大小在流中有序地进行排列。

作为例子,图10-1 展示了一个记录用户访问轨迹的流 visits,这个流包含了 ID 为 1100000000000-0、1200000000000-0 和 1300000000000-0 的 3 个元素,它们每个都包含有 3 个键值对,这些键值对分别用于记录网站的访客、被访问的位置以及访客停留的时长。比如,根据 ID 为 1100000000000-0 的流元素的显示,名为 peter 的用户访问了位置 /book/10086,并在该位置停留了 150s。

image 2025 01 05 11 13 49 782
Figure 1. 图10-1 一个记录用户访问轨迹的流

与之前介绍过的其他数据结构一样,Redis 也为流提供了非常丰富的操作 API,本章接下来将对这些 API 做进一步的介绍。