【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

作为一个纯粹数据结构的 Redis Streams

  • 2019-12-09
  • 本文字数:3599 字

    阅读完需:约 12 分钟

作为一个纯粹数据结构的Redis Streams

Redis 5 中引入了一个名为 Streams 的新的 Redis 数据结构,吸引了社区极大的兴趣。接下来,我会在社区里进行调查,同用户们谈谈他们在实际生产中的使用场景,然后写个博客记录一下。


今天我想解决另一个问题:我有点怀疑许多用户仅仅把 Streams 作为解决类似 Kafka 所要解决的问题的一个手段。实际上,这个数据结构,在当初设计的时候,在生产者/消费者消息通信的场景下,也是可以用起来的。而且我意识到 Streams 是很擅长这个场景的,用法也很简洁。Streaming 是一个很好的模式和“思维模型”,在被用来设计系统时,可以获得巨大的成功。但是 Redis Streams 就像大多数 Redis 数据结构一样,是比较通用的结构,可以用来对许多不同的问题进行建模。在本篇博文中,我将聚焦在作为纯粹数据结构的 Streams,完全忽略其阻塞式的操作、消费者群组和所有和消息通讯有关的部分。

作为 CSV 文件加强版的 Streams

如果你要把一系列结构化的数据项记录下来,并且觉得用数据库毕竟有点“杀鸡用牛刀”,那么你可能会说:让我们以“仅追加”(append only)模式打开一个文件,然后把每一行作为 CSV(逗号分隔的值)格式记录下来:


(以 append only 模式打开 data.csv 文件)


time=1553096724033,cpu_temp=23.4,load=2.3


time=1553096725029,cpu_temp=23.2,load=2.1


看起来是很简单的,是吧,人们一直也是这么做的:这是一个一致的模式,如果你知道你在做什么的话。但是和这个(文件)模式对等的 in-memory(内存)模式是怎样的呢?内存比 append only 文件更强大,自然也就没有类似 CSV 文件的一些限制:


  1. 做范围查询比较难(效率低);

  2. 太多冗余信息:每条记录中的时间差不多是一样的,而且许多列都是重复的。同时,在你想切换到不同的一组列时,如果移除这些冗余信息,这会使得格式的灵活性更低。

  3. 数据项的位移就是文件中的字节位移:如果我们改变文件的结构,那么位移值就会是错的,所以实际上这里没有真正的 primary Id 的概念。

  4. 我不能移除这些数据条目,在没有 GC(垃圾收集)能力的情况下,只能将他们标记为“失效”,如果不重写 log(日志)的话。而且因为某些原因,日志重写的性能很差,如果能够避免的话,就再好不过了。


从另外一个角度看,这些 CSV 条目的日志也有好的方面:他们没有固定的结构,数据列可以变化,容易生成,而且毕竟其结构也是比较紧凑的。Redis Streams 的设计理念就是取长补短,其结果就是一个和 Redis Sorted sets 非常类似的混合型数据结构:他们看起来像是一个基础数据结构,为了达到这样一个效果,在底层他们有多种表现形式。

Streams101

(如果你已经了解 Redis Streams 的基础的话,可以跳过这个部分)


Redis Streams 由差分压缩(delta-compressed)的宏节点表示,这些节点通过基数树(radix tree)连接在一起。其效果就是,可以非常快的进行随机查找、按需获取范围、删除老的数据项,从而创建一个带上限的 stream,等等。同时,给程序员的接口和 CSV 文件是非常类似的:


> XADD mystream * cpu-temp23.4 load 2.3"1553097561402-0"> XADD mystream * cpu-temp 23.2 load 2.1"1553097568315-0"
复制代码


从上面的例子我们看到,XADD 命令自动产生和返回了记录 ID,记录 ID 是单调递增的,由 2 个部分组成:<时间>-<计数器>,时间以毫秒表示,对于在同一毫秒中产生的记录,计数器会递增。


以“只追加(append only)CSV 文件”的思想作为基础,我们构建的第一个新的抽象是:既然我们使用星号作为 XADD 命令的 ID 参数,从服务侧我们就可以免费得到记录 ID。这个 ID 不仅可以用来指示一个 stream 中的某一条数据记录,也关联了这条记录加入 stream 的时间。实际上,XRANGE 命令既可以做范围查询,也可以查询单条记录。


> XRANGE mystream1553097561402-0 1553097561402-01) 1) "1553097561402-0"   2) 1) "cpu-temp"      2) "23.4"      3) "load"      4) "2.3"
复制代码


在这个例子中,为了标识单个元素,我使用了相同的 ID 作为范围查询的起止条件。但是,我也可以使用任何范围条件,加上一个 COUNT 参数来限制查询结果的个数。同样的,也不必详细指明完整的 ID 作为范围条件,可以只用 ID 的 Unix 毫秒时间戳部分,来获取给定时间范围内的元素。


> XRANGE mystream1553097560000 15530975700001) 1) "1553097561402-0"   2) 1) "cpu-temp"      2) "23.4"      3) "load"      4) "2.3"2) 1) "1553097568315-0"   2) 1) "cpu-temp"      2) "23.2"      3) "load"      4) "2.1"
复制代码


现在,没必要展示更多的 Streams API 了,详细的内容可以参考 Redis 文档。让我们聚焦在其使用模式上:XADD 用来添加元素,XRANGE(也包括 XREAD)是用来获取范围内的元素(取决于你的目的),让我们看下为什么我把 Streams 称为一个如此强大的数据结构。


如果你想对 Streams 及其 API 了解更多的话,请一定看下这篇教程(长按复制链接):https://redis.io/topics/streams-intro

网球选手

几天前我和一个最近正在学习 Redis 的朋友一起对一个应用进行建模,这个应用是用来记录本地的网球场、本地的选手和比赛的。用来对选手建模的方法是显而易见的:一个选手是一个小的对象,所以一个 hash 值加上选手:< id >的键就够了。当你使用 Redis 作为首要的应用数据建模的手段,你会马上意识到,你需要一个方法来记录在一个给定网球俱乐部中举行的比赛。如果选手 1 和选手 2 打了一场比赛,选手 1 赢了,我们可以在一个 stream 中记录如下:


> XADD club:1234.matches *player-a 1 player-b 2 winner 1"1553254144387-0"
复制代码


通过这个简单的操作,我们得到了:


  1. 一个唯一的比赛 ID:stream 中的 ID;

  2. 不需要为了标识一场比赛而创建一个对象;

  3. 免费的范围查询可以对比赛记录进行分页,也可以查看在过去一个给定时刻的比赛记录;


在 Streams 出现前,我们需要创建一个按时间排序的 sorted set。sorted set 中的元素就是比赛的 ID,同时还需要作为 hash 值保存在一个不同的 key 中。这不仅意味着更多的工作,同时也带来了难以想象的内存浪费。还有更多的你能想到的情况(后面可以看到)。


目前,可以看到的一点是,Redis Streams 就是一种处于仅追加模式(append only)的 Sorted Set,以时间作为键,每个元素是一个小的 hash 值。在对 Redis 进行建模的场景下,带来革命性的一点就是他的简洁。

内存使用

上述用例不仅意味着一个从行为上看更为一致的模式。比起老的 Sorted set + hash 的方式,Stream 方案的内存开销是如此之低,以至于之前不具有可行性的东西,现在完全是可行的。


以下数字是按之前的配置计算的、保存 100 万条比赛数据的开销:


Sorted Set + Hash 内存开销 = 220 MB (242 RSS)


Stream 内存开销 = 16.8 MB (18.11 RSS)


这超过了一个数量级的差别(准确的说是 13 倍的差别),而且这意味着那些之前在内存中开销太大的用例,现在完全是可行的。神奇的地方就在于 RedisStreams:宏节点可以包含多个以 listpack 数据结构、非常紧凑的方式编码的元素。例如,即使整数在语义上是字符串,但 listpack 可以把他们编码为二进制形式。在这个基础上,我们可以进行差分压缩和“相同列”的压缩。同时,因为宏节点在基数树(在设计上仅占用很少的内存)中链接在一起,我们也可以通过 ID 和时间进行查询。所有这些加在一起,使得内存占用很少。有意思的是,在语义上,用户看不到任何使得 Streams 如此高效的实现细节。


现在,让我们做一个简单的计算。如果我可以用 18MB 的内存存储 1 百万条记录,180MB 存 1 千万条,1.8GB 存 1 亿条记录。如果有 18GB 内存的话,可以存 10 亿条记录。

时间序列

依我看,我们需要重点关注的是,上述我们使用 Stream 表示网球比赛的用法,在语义上,同使用 Stream 处理一个时间序列是完全不同的。是的,逻辑上我们仍然在记录某种事件,但一个重要的区别是,在一种场景下,我们记录和创建记录条目来呈现对象;在时间序列场景下,我们只是测量某些外部发生的事情,而这并不会表示成一个对象。你可能认为这个区别不重要,但其实不然。对于 Redis 用户,重要的一点是需要建立一个概念,Redis Streams 可以用来创建具有全序的小对象,每个对象都有一个 ID。


时间序列是一个最基础的使用场景,显然,也是最重要的使用场景,但在 Streams 出现前,Redis 对这种场景是有些无能为力的。Streams 的内存特性和灵活性,加上带上限的 stream(capped stream)的能力(参考 XADD 命令的参数选项),在开发者的手中是一个非常有力的工具。

结论

Streams 是非常灵活的,而且有很多使用场景。好了,话不多说,上述的例子我想要传达的一个关键信息就是关于内存使用的分析,也许对于许多读者来说这已经很明显了,但是最近几个月和人们的交谈给我一种感觉,在 Streams 和 Streams 的使用场景之间有着很强的关联性,就好像这个数据结构只擅长这种场景一样,但其实不是这样的。:-)


本文转载自公众号中间件小哥(ID:huawei_kevin)。


原文链接:


https://mp.weixin.qq.com/s/k4aOXSLHQALt_od68A33JQ


2019-12-09 13:431833

评论

发布
暂无评论
发现更多内容

智慧警务开发,二维码定位报警系统搭建

t13823115967

智慧公安 智慧公安扫码

某美女的程序员老公半夜都还不回家,原来是偷偷在公司看Redis+JVM+Spring cloud+MySQL技术文档

Java架构之路

Java 程序员 架构 面试 编程语言

还有谁比阿里人更懂SpringCloud Alibaba 呢?P8大牛纯手打笔记免费分享!

Java架构之路

Java 程序员 架构 面试 编程语言

助力孩子走上学霸之路,K12学习神器现已面世!

E科讯

港美股交易系统开发框架构造简述篇

软件开发大鱼V15988750073

国际配售 港股交易系统开发 证券交易系统 资管系统 港股打新系统

SGY奇点交易所系统软件开发|SGY奇点交易所APP开发

系统开发

《数据结构与抽象:Java语言描述》.pdf

田维常

数据结构

四面腾讯pcg后端开发岗,一个星期面完成功拿到20K的offer。分享面经

Java架构之路

Java 程序员 架构 面试 编程语言

区分Protobuf 3中缺失值和默认值

Gopher指北

protobuf Go 语言

JS&Swift

ios swift

Mybatis动态映射,so easy啦

田维常

用60行代码实现一个高性能的圣诞抽抽乐H5小游戏(含源码)

徐小夕

Java 大前端 H5游戏 H5

阿里架构师经验分享!啃完999页Android面试高频宝典,面试心得体会

欢喜学安卓

android 程序员 面试 移动开发

区块链信息共享应用落地搭建解决方案

t13823115967

区块链+ 区块链应用 信息共享

iOS面试基础知识 (一)

iOSer

ios 面试 runtime 编程开发 iOS Developer

挖矿矿池系统开发详情丨挖矿矿池源码案例

系统开发咨询1357O98O718

挖矿矿池系统开发案例 旷工系统开发功能

Github 2020 年度报告:你以为新冠击溃了开发者?不!他们创造了更多代码...

阿里巴巴云原生

开源 Serverless 程序员 代码

区块链BaaS应用平台开发

13828808769

恕我直言!有了这份MySQL学习文档,你收藏夹里的其他MySQL学习资料都可以扔了

Java架构之路

Java 程序员 架构 面试 编程语言

架构师训练营第八周作业

李日盛

算法

DeFi流动性挖矿系统开发(案例源码开发)

系统开发咨询1357O98O718

defi流动性挖矿系统开发

四币连发平台系统开发详解丨四币连发源码(案例)

系统开发咨询1357O98O718

四币连发系统开发案例详解

超详细讲解!Android面试真题解析火爆全网,搞懂这些直接来阿里入职

欢喜学安卓

android 编程 程序员 面试 移动开发

服务于阿里、滴滴、华为等一线互联网公司的分布式消息中间件RocketMQ核心笔记

Java架构追梦

Java 架构 面试 RocketMQ 消息中间件

DeFi流动性挖矿系统开发详解方案

系统开发咨询1357O98O718

defi流动性挖矿系统开发

Scala中String和Int隐式转换的问题分析

木子李G

scala 大数据 编程 隐式转换

新思科技最新报告显示开源安全是首要考虑因素

InfoQ_434670063458

仅凭这份Java大纲笔记,我如愿拿到了阿里offer。

Java架构之路

Java 程序员 架构 面试 编程语言

请问如何短时间突击 Java 通过面试?

Java架构师迁哥

EPBC环保生态链系统开发案例丨环保生态链EPBC源码平台

系统开发咨询1357O98O718

环保链APP系统开发案例

刚拿到蚂蚁金服架构师offer!大佬教你如何成为offer收割机

比伯

Java 编程 架构 面试 计算机

作为一个纯粹数据结构的Redis Streams_文化 & 方法_中间件小哥_InfoQ精选文章