前沿观察 | Redis Streams原生数据结构科普

netcat20000
发布于 2022-7-14 16:06
浏览
0收藏

 

Redis 5.0中以引入的新Redis数据结构“Streams”引起了社区的极大兴趣。

 

不久之后,我想进行社区调查,与有生产用例的用户交谈,并撰写博客。今天我想解决另一个问题:我开始怀疑很多用户只是将Streams作为解决Kafka(TM)类似场景的方案。但实际上,Stream数据结构也被设计为在生产者和消费者消息传递的场景使用,但是认为Redis Streams仅仅对这个场景有用是不够的。

 

Stream是一种极好的模式和“心智模型”,可以在系统设计中取得巨大成功,但Redis Streams与大多数Redis数据结构一样,更为通用,可用解决十几种不同场景的问题。因此,在这篇博文中,我将把Streams作为一个纯数据结构来关注,完全忽略它的阻塞操作、用户组和所有消息传递部分。

 

Streams是更高性能的CSV

 

如果你想要记录一系列结构化数据项,并且认为数据库被高估了,那么您可以这样说:让我们以追加模式打开一个文件,并将每一行记录为CSV(逗号分隔值)项:

(open data.csv in append only)

time=1553096724033,cpu_temp=23.4,load=2.3

time=1553096725029,cpu_temp=23.2,load=2.1

 

看起来很简单,人们做了很多年并且仍然这样做:如果你知道自己在做什么,这是一个固定的模式。但是内存相当于什么呢?内存比附加文件更强大,可以自动优化CSV文件的限制:

1. 在这里进行范围查询很困难(效率低下)。

2. 冗余信息太多:每个条目的时间几乎相同,字段重复。如果我为了切换到另一组字段删除它,又会使格式变得不太灵活。

3. 项偏移只是文件中的字节偏移量:如果我们更改文件结构,则偏移量将是错误的,因此这里没有实际的主要ID概念。条目基本上不会以某种方式被单独处理。

4. 我不能删除条目,但是如果不能通过重写日志,我只能在没有垃圾收集功能的情况下将它们标记为无效。由于几个原因,日志重写通常很糟糕,如果可以避免,那就很好。

 

尽管如此,CSV条目的日志在某种程度上还是非常棒的:没有固定的结构,字段可能会更改,生成起来很简单,而且毕竟非常紧凑。Redis Streams的理念是保留好东西,但要克服限制。其结果是一个与Redis排序集非常相似的混合数据结构:它们感觉像一个基本的数据结构,但是为了获得这样的效果,在内部它使用多个表示形式。

 

Streams 101(如果你已经知道Redis Stream的基础知识,你可以跳过它)

 

Redis Streams表示为由基数树链接在一起的delta压缩宏节点。效果是能够以非常快的方式寻找随机条目,在需要时获得范围,移除旧项目以创建加盖流,等等。然而,我们与程序员的接口非常类似于CSV文件:

 

> XADD mystream * cpu-temp 23.4 load 2.3

"1553097561402-0"

> XADD mystream * cpu-temp 23.2 load 2.1

"1553097568315-0"

 

从上面的示例中可以看出,XADD命令自动生成并返回条目ID,它是单调递增的,有两部分:<time>  -  <counter>。时间以毫秒为单位,在相同毫秒内生成的条目的计数器会增加。因此,在“追加模式CSV文件”概念之上的第一个新抽象是,因为我们使用星号作为XADD的ID参数,所以我们从服务器获得免费的条目ID。此类ID不仅可用于指向stream中的特定项,还与将条目添加到stream中的时间相关。事实上,使用XRANGE,可以执行范围查询或获取单个项目:

 

> XRANGE mystream 1553097561402-0 1553097561402-0

1) 1) "1553097561402-0"

   2) 1) "cpu-temp"

      2) "23.4"

      3) "load"

      4) "2.3"

 

在这种情况下,我使用相同的ID作为范围的开始和停止,以便识别单个元素。但是我可以使用任何范围和COUNT参数来限制结果的数量。类似地,不需要将完整ID指定为范围,我可以使用ID的毫秒unix时间部分来获取给定时间范围内的元素:

 

> XRANGE mystream 1553097560000 1553097570000

1) 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像数据一样强大结构体。但是,如果您想了解有关Redis Streams及其API的更多信息,请务必访问以下教程:

https ://redis.io/topics/streams-intro

 

网球运动员

 

几天前,我和一个正在学习Redis的朋友一起制作了一个应用程序:一个用来跟踪当地网球场、当地球员和比赛的应用程序。用Redis为播放器建模的方式非常明显,播放器是一个小对象,所以只需要一个散列,键名为player:。你在Redis中模拟玩家的方式非常明显,玩家是一个小对象,所以你需要一个Hash,其中的关键名称如player:<id>。当您进一步对应用程序数据建模时,要使用Redis作为它的主要工具,你会立即意识到需要一种方法来跟踪在给定网球俱乐部中玩的游戏。如果玩家:1和玩家2玩游戏,玩家1赢了,我们可以在流中写下以下条目:

 

> XADD club:1234.matches * player-a 1 player-b 2 winner 1

"1553254144387-0"

 

通过这个简单的操作,我们有:

1. 匹配的唯一标识符:stream中的ID。

2. 无需创建对象即可识别匹配项。

3. 范围查询免费分页匹配项,或检查在过去某个给定时刻所进行的匹配项。

 

在Streams之前,我们需要创建一个按时间划分的排序集:排序的集合元素将是匹配的ID,作为哈希值存在于不同的密钥中。这不仅仅是更多的工作,它还浪费了大量的内存。更多,比你能猜到的还要多(见后文)。

 

现在要说明的是,Redis Streams是一种排序集,在追加模式中,按时间键入,每个元素都是一个小哈希。简单来说,这是Redis建模领域的一场革命。

 

内存使用情况

 

上面的用例不仅仅是一个更可靠的模式问题。与旧的方法相比,Stream解决方案的内存成本是如此不同,旧的方法是为每个对象设置一个排序集+散列,这使得某些东西不可行,现在完全没问题。这些是在先前公开的配置中存储的一百万个匹配的数字:

 

排序集+哈希内存使用量= 220 MB(242 RSS)

Stream内存使用量= 16.8 MB(18.11 RSS)

 

这不仅仅是一个数量级的差异(准确地说是13倍的差异),这意味着昨天对于内存来说过于昂贵的用例现在是完全可行的。神奇之处在于Redis流的表示:宏节点可以包含几个元素,这些元素以一种非常紧凑的方式编码在名为listpack的数据结构中。例如,即使整数是语义上的字符串,listpack也会注意以二进制形式编码整数。在此基础上,我们应用delta压缩和相同字段压缩。然而,我们可以通过ID或时间来查找,因为这样的宏节点是在基数树中链接的,而基数树的设计也是为了使用很少的内存。所有这些因素加在一起导致了低内存使用量,但有趣的是,从语义上来说,用户看不到任何使Stream有效的实现细节。

 

现在让我们做一些简单的数学运算。如果我可以在大约18 MB的内存中存储100万个条目,我可以在180 MB中存储1000万个,在1.8 GB中存储1亿个。只有18 GB的内存,我可以拥有10亿个项目。

 

时间序列

 

需要注意的一点是,在我看来,上面我们使用流来表示网球比赛的用法与在时间序列中使用Redis Stream的用法在语义上“非常不同”.是的,从逻辑上讲,我们仍在记录某种事件,但一个根本区别在于,在一种情况下,我们使用日志记录和条目的创建来呈现对象。在时间序列的情况下,我们只是计量外部发生的事情,这并不代表一个对象。你可能认为这种差异微不足道,但事实并非如此。

 

对于Redis用户来说,重要的是要构建这样一个概念,即可以使用Redis流创建具有总顺序的小对象,并为这些对象分配id。然而,即使是时间序列最基本的用例,很明显,在这里也是一个很大的用例,因为在Streams之前,Redis对于这样的用例是没有希望的。Streams的内存特性和灵活性,加上封顶流的能力(参见XADD选项),是开发人员手中非常重要的工具。

 

结论

 

Streams是灵活的,有很多用例,但是我想把这篇博客文章缩短,以确保在上面的例子和内存使用分析中有一个明确的实用信息。对于许多读者来说,这可能已经很明显了,但在过去几个月与人交谈让我觉得Streams和streaming用例之间存在强烈关联,就好像数据结构只是擅长这样,但其实事实并非如此。

 

版权声明:本文由腾讯云数据库产品团队整理,页面原始内容来自于db weekly英文官网,若转载请注明出处。翻译目的在于传递更多全球最新数据库领域相关信息,并不意味着腾讯云数据库产品团队赞同其观点或证实其内容的真实性。如果其他媒体、网站或其他任何形式的法律实体和个人使用,必须经过著作权人合法书面授权并自负全部法律责任。不得擅自使用腾讯云数据库团队的名义进行转载,或盗用腾讯云数据库团队名义发布信息。原文地址:http://antirez.com/news/128  

 

文章转自公众号:腾讯云数据库

分类
标签
已于2022-7-14 16:06:12修改
收藏
回复
举报
回复
    相关推荐