本文是Redis作者antirez的一篇博客

原文地址:antirez.com/news/128

我们在Redis5版本迎来了一个新的数据结构,它的名字叫做"Streams"。(撒花)Streams一经推出,就引起了社区中各位大佬的关注。所以我决定过一段时间做一个社区调查,讨论一下它的使用场景,并会在博客中将结果记录下来(是Redis作者的博客)。今天我想聊的是另一个问题:我怀疑有很多用户认为Streams的使用场景是和Kafka一样的。实际上,这个数据结构的设计背景也是消息的生产和消费,但你应该认为Redis Streams只是更擅长做这样的事情。流是一种很好的模型和"心理模型",它能帮助我们更好的设计系统,但是Redis Streams像其他Redis数据结构一样,它更加通用,可以用来处理更多不同的问题。所以这篇博客我们会重点关注Redis Streams作为一种数据结构有哪些特性,而完全忽略它的阻塞操作、消费群和所有消息相关的内容。

Streams是steroids上的CSV文件

如果你想记录一系列的结构化数据,并且确定数据库是足够大的,你可能会说:我们以追加写入的方式打开一个文件,每一行记录是一个CSV数据项:

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 Sorted Set这样的混合数据结构——Redis Streams。他们看起来像基本数据结构一样,但是为了得到这样的效果,内部是有多种表现形式的。

Streams 101(就是Streams基础部分)

Redis Streams是一种通过基数树连接的增量压缩的宏节点。(好难理解的概念,把原话贴出来:Redis Streams are represented as delta-compressed macro nodes that are linked together by a radix tree)。它的作用是,快速查找一个随机项,获取范围值,删除旧值来创建一个有大小上限的流。对程序员来说,我们的接口和CSV文件很相似:

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

通过这个例子可以看到,XADD命令自动生成并返回了一个entry ID。它是单调递增的,并且有两部分组成,<时间>-<数量>,时间是毫秒级,而数量则是同一毫秒生成的entry数量递增。

所以第一个从上面所说的"追加写入CSV"文件抽象出来概念就是,如果用星号作为XADD命令的ID参数,就从服务器获取了一个entry ID。这个ID不仅仅是entry的唯一标识,也和entry加入流的时间有关。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参数限制结果的数量。我也可以将起止参数都设置为时间戳,获取一段时间内的数据项。

> 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了。我们有相关的文档,感兴趣的同学可以去阅读。目前为止,我们只需要关注基本使用方法:XADD用来增加数据,XRANGE(或XREAD)用来读取数据。我们来看一下我为什么说Streams是一个强大的数据结构。

网球运动员

前几天我和一个最近在学习Redis的朋友一起建模一个应用程序:这是一个用来追踪当地网球场、球员和比赛的app。很明显,球员是一个小的模型,在Redis中只需要用一个hash就足够了,key的形式可以是player:<id>。当你进一步使用Redis建模时,就会意识到你需要去追踪指定网球俱乐部的一场比赛。如果球员1和球员2打了一场比赛,球员1获胜。那么我们可以这样来记录:

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

通过这样简单的操作,我们就可以获得如下的信息:

  1. 一场比赛的唯一标识:流里的ID
  2. 不需要创建一个表示比赛的对象
  3. 分页查询比赛情况,或者查看某场比赛是否在指定时间就进行

在Streams出现之前,我们需要创建一个Sorted Set,分数是时间。Sorted Set的元素是比赛的ID(存在一个Hash里)。这不仅是增加了工作量,而且还造成了更多的内存浪费,比你想象的要多得多。

现在看起来Streams像是一个追加模式的,以时间为分数,元素是小型Hash的Sorted Set。简而言之,这是Rediscover建模环境中的一次革命。

内存使用情况

上面的例子不仅仅是固化模式的问题,相比旧有的Sorted Set+ Hash的模式,Streams对内存的节省做了很好的优化,然而这一点是不容易被发现的。

假设我们要记录100万场比赛,

Sorted Set + Hash的内存使用量是220MB(242RSS)

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

这不仅仅是一个数量级的差异(实际上是13倍的差异),这意味着我们旧有的模式实在是太浪费了,而新的模式是完美可行的。Redis Streams还有其他神奇的地方:宏节点可以包括多个元素,它们使用叫做listpack的数据进行编码。listpacks会对二进制形式的整数进行编码,即时它是语义字符串。最重要的是,我们使用了增量压缩和相同字段压缩。我们可以通过ID或时间进行查询,因为宏节点是用基数树连接的。基数树叶被设计为使用很少的内存。所有的事情都使用极少的内存,但有趣的是,用户并不能从语义上看到使Streams更加高效的实现细节。

现在我们来做一个简单的计算,如果我保存了100万个entry,使用了18MB内存,那么1000万个就是180MB,1亿个使用1.8GB,保存10亿数据也只使用18GB内存。

时间序列

有一个比较重要的事情需要注意,在我看来,上面我们用来记录网球比赛的例子与把Redis Streams作为一个时间序列来使用非常不同。没错,逻辑上我们仍然是记录一类事件,但本质上的区别是记录日志和创建一个entry并存入对象的不同。在使用时间序列时,我们只是记录一个外部事件,而不需要真的展示一个对象。你可能认为这个区别不重要,但事实不是这样。对Redis用户来说很重要的是,如果需要保存一系列有序的对象,并且给每个对象赋一个ID,那么就需要使用Redis Streams。

然而即时是一个简单的时间序列,也是一个很大的用例,因为在Streams出现之前,Redis在面对这种用例时令人有些绝望。一个节省内存,并且灵活的流,对开发者来说是一个重要的工具。

结论

Streams非常灵活并且有很多使用场景,我想尽量用简短的语言,以确保上面的例子和内存分析更加通俗易懂。也许大多数读者已经搞懂了。不过在上个月我和别人交流时感觉到Streams和流式处理还是有着很强的关联。就像这个数据结构只能用来处理流一样,事实并非如此。

转载于:https://juejin.im/post/5c9e1ae551882567c467b386

【译】Redis喜提新数据结构:Redis Streams相关推荐

  1. 华为m6升级鸿蒙,华为鸿蒙系统再传喜讯!14款华为旧旗舰喜提新系统:大幅度换血...

    [1月23日讯]相信大家都知道,自从华为鸿蒙OS系统2.0手机Bate版本正式发布以后,前期只有华为Mate 30系列.华为P40系列以及一款华为MatePad产品,才可以申请体验鸿蒙OS 2.0的B ...

  2. 镁客网每周硬科技领域投融资汇总(3.24-3.30),FF喜提新救主...

    阿里巴巴收购第二家以色列创企. 本周硬科技领域投融资事件一共58起,人工智能领域发生26起融资事件和2起收购事件,占比49%:生物医药领域发生16起融资事件和1起收购事件,占比29%:物联网法神4起融 ...

  3. Node.js 与 JavaScript 基金会正式合并,JS 喜提新主场

    翘首以盼了 6 个月后,JavaScript 终于迎来了自己的"新家落户"--OpenJS 基金会于今日官宣! 图源:Node.js Foundation 如何挑战百万年薪的人工智 ...

  4. 实锤了!程序员喜提新头衔,瞬间破防!!

    码农--编码的农民,现在各行各业对计算机应用和依赖都在不断增强,随之而来的是大量对IT行业开发人才要进入到编码工作中,这些开发人员被称为"码农",有外行的戏称,也有内行的自嘲. 学 ...

  5. 1.2(redis)5大数据结构

    文章目录 redis的5大数据结构 Redis(key)的操作 redis是Nosql数据库 常用命令 String 介绍 常用命令 数据结构 List 介绍 常用命令 数据结构 Set 介绍 常见命 ...

  6. redis创建像mysql表结构_如何给redis添加新数据结构

    前言 作为一款缓存型nosql数据库,redis在诞生之初就以高性能.丰富的数据结构等特性获得业界的青睐.redis默认提供了五种数据类型的支持:string.list.set.zset.hash.针 ...

  7. 8.16 Redis的新数据结构、配置文件及使用

    BitsMaps 可以将它想象成一个由0和1构成的数组,数组下标为偏移量 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCLFD2g7-1629171618227)( ...

  8. 一文带你深入理解Redis中的底层数据结构,再也不怕不懂数据类型的底层了

    数据结构前言 都说Redis快,因为什么呢?只是因为它是内存数据库,所有操作都是基于内存进行的吗?其实不然,这与它的数据结构也是密不可分的.下面我们就来了解一下Redis的数据结构. Redis 数据 ...

  9. Redis大通关系列-数据结构深入

    官网地址:https://redis.io 一.Redis是做什么用的? Redis is an open source (BSD licensed), in-memory data structur ...

最新文章

  1. Spring Cloud 学习二(Eureka)
  2. 图片日志:深拷贝和浅拷贝的区别/序列化及反序列化
  3. Python 2.7 Tutorial —— 流程控制
  4. oracle 库存管理系统,库存管理系统
  5. Java内存模型_基础
  6. 详解 ConcurrentHashMap
  7. Linux简单实用小技巧
  8. Android项目中,在一个数据库里建立多张表
  9. 上万规模数据湖如何在实验室测试
  10. 特征选择算法-Relief
  11. 乐橙tp1 html调用,乐橙TP1的妙用
  12. 进击的巨人有趣表情包
  13. \t\tP2P终结者原理
  14. 【转载】C语言嵌入式系统编程修炼之二:软件架构篇
  15. 利用Python编写脚本批量下载公众号中的音频
  16. STM32学习笔记(5) 串口通讯-接收与发送
  17. P6-Windows与网络基础-安装eNSP软件环境
  18. 你了解净水器滤芯知识多少?
  19. 图像处理(数字图像处理)
  20. 多面体及欧拉公式及广义欧拉公式

热门文章

  1. git clone 某次提交前代码_git提交代码常用命令
  2. Java——集合经典面试题
  3. linux输入子系统
  4. js原生事件委托的实现
  5. 神盾解密工具 之 解密 “ PHP 神盾解密工具 ”
  6. 优化基于ExtJS 4.1的应用
  7. (转)linux下vi编辑器编写C语言的配置
  8. 配置修改Nginx支持 PATHINFO
  9. Python 利用pymupdf将pdf转换为图片并拆分,后通过PIL合并生成一张图片
  10. Jedis的使用及SpringBoot整合Redis