数据结构论坛

首页 » 分类 » 定义 » Redis干货分享在Redis中解锁
TUhjnbcbe - 2021/8/17 9:19:00

来源:Redislabs

作者:KyleDavis

翻译:Kevin

大多数开发人员都知道Redis具备实时响应请求的能力,这样的特性使其非常适合处理时间序列数据。但是时间序列数据到底是什么?业界存在各种各样不同的解释,但本文认为可以简化它的定义:时序数据通常有一个特性:它是将用时间序列作为索引的一种数据编码,并且每个记录的时间都有一个数字值。如果将其可视化为两列,则一列将由时间索引组成,通常为Unix的时间戳。另一列将由某种数值组成。非常简单。最重要的特点是:您可以使用时间范围来分析时间序列数据,例如查看1月1日至1月3日之间发生的事件。您还可以将时间细化到秒级,甚至到毫秒级别。您还可以将数据按照不同的时间单位进行区分,例如查看每小时发生的时间。然后,如果您不想查看时间序列数据中的单个事件,则可以在此之上进行聚合,例如可以取每小时的平均值。许多人想到时间序列数据时便会想到股票行情图,查看某只特定股票在指定时间范围内的具体趋势只是时间序列数据的一个应用。另外一种我经常使用时间序列数据的示例在我需要查看某个特定时间段内某台服务器上的CPU负载情况。时间序列数据也是物联网领域一种查看传感器数据或其他信息的好方法。每当您查看一段时间内的数据的趋势时,通常都需要用到某种时间序列数据库或时间序列结构。

时间序列数据在Redis中的发展历史

现在,让我们集中讨论Redis和时间序列。一切都始于Redis对于有序集合的支持(zset),它是Redis中的内置数据结构之一。人们很早就开始使用zset对时间序列数据使用排序集,看起来像这样:

ZADDmySortedSet00示例中包含了一个ZADD命令,mySortedSets作为键的名字,使用时间戳作为zset的score,最后将需要排序的值的内容作为zset的成员。这样的操作很好,但是问题在于您只能根据具体的范围来获取zset的值,而不能进行平均值或者降采样。同时,集合中的元素不能重复。如果有两个值相同的元素具有相同的时间戳,则这种基于集合的操作则可能存在问题。因此,在下面的示例中,第二个值实际上会覆盖第一个值。这里就不适合使用集合来进行时间序列数据的计算

ZADDmySortedSet00ZADDmySortedSet00开发人员提出了许多规避的方法,这些规避的方法通常在计算上很复杂,而且比较难实现。因此必须有一种更简单的方法。

RedisStreams特性是一个解决办法

大约两年前,Redis4.0首次发布了RedisStreams特性,该特性旨在解决系统中统一收集日志和进程间消息传递的问题。对于时序用例来说,RedisStreams提供了一个SortedSets中非常有用的特性。它允许对每个样本的键值对自动生成不重复的ID。

XADDmyStream*myValueXADDmyStream*myValueanotherFieldhello在第一条命令中,我们将字段myField设置为。第二条命令中,创建了一个新的入口,其中myValue设置为,而anotherField设置为hello。这些都是创建在myStream这条流上。但Redis中的Stream依然缺少一些时间序列中常用的重要特性,并且它不是针对时间序列数据而设计。使用Stream可以获取时间范围,但功能也仅限于此。现在我们来看看Redis中的Modules提供的API能够做些什么,Redis中支持modules的API比支持Streams这个特性要早,在Modules中允许Redis可以支持一些扩展的数据类型。Redis用户可以使用Modules来实现各种各样的扩展特性。现有的常用模块有:RediSearch、RedisGraph、RedisJSON等等。RedisTimeSeries模块可以在Redis内创建时间序列数据库。

RedisTimeSeries模块是如何工作的?

在开始使用RedisTimeSeries模块之前,重要的是要了解它的原理。首先需要了解的是“块”。实际上,您从不直接操纵“块“,但是RedisTimeSeries将所有的时序数据存储在这些块中。每个块均由双向链表中的两个相关数组组成(一个用于时间戳,一个用于样本值)。假设我要在我的时间序列数据库中添加一个时间戳。它位于两个数组的第一行。如果您还有其他样本,会被直接插入到数组中。块的大小是固定的。当块填满的时候,其他数据将自动存储到下一个块。在链表的头或尾插入块是不怎么耗费计算资源的,因此在添加新块时,耗时非常短。与大多数Redis数据类型不同的是,最佳做法是首先创建时间序列键,使用的命令是TS.CREATE。这里创建的键是myTS。在这里让我们假设要往这个键里面增加一些元数据。想象一种场景,我们正在经营一个蔬菜苗圃,并且想要跟踪4号温室中的47号白菜;将其这个信息称为元数据标签。这适用于整个时间序列中的每个样本:处理时间序列数据的另一个重要部分是数据保持。假设我们不关心任何超过60秒的数据。RedisTimeSeries可以去除需要保留的时间段之外的数据。我们可以使用TS.ADD命令向键中添加值。命令的第一个参数是键的名字myTS,星号是从RedisStreams中借鉴的语法,这会让Redis将自动生成时间戳。在这里,它的值为。让我们再做一个示范,并指定一个时间戳。需要注意的是,时间戳实际上只能追加,所以您不能向最后一次使用的时间戳之前添加数据。后续使用TS.ADD添加的值必须是大于最后一个值的时间戳。接下来,假设您需要获取两个时间戳之间的所有数据样本。在这个示例中,您可以看到第一个时间戳的值为,第二个时间戳的值为。以上这些虽然很有用,但也许您还想要获取每30秒时间的平均值,那么可以使用avg关键字,所得到的结果是和1,的平均值。当键中拥有更多数据时会怎么样呢?您可能不想一直使用TS.RANGE命令,而只想获取某一个具体的数据。上面这些要求我们也可以通过RedisTimeSeries做到。myTS是一个键(这是源),目标是第二个键:myTS2。在这里所有块均表示30秒的时间,RedisTimeSeries会自动将它放入目标键中。所以每隔30秒都会有一个额外的数据样本添加到键myTS2中。RedisTimeSeries支持的不仅限于平均值。你可以用它求和,可以得到最小值,可以得到最大值,可以得到范围,可以用来计数(有多少个),获取第一个或最后一个。TSRANGE能支持所有这些不同的聚合功能。

了解RedisTimeSeries中的更多命令

让我们看看RedisTimeSeries还能做什么。TS.INCRBY和TS.DECRBY命令可以用在时间序列中的计数场景。TS.INCRBY可以在前一个数据上增加某个值。假设您需要在10秒钟内收集了10个权重值。在一个键上执行TS.INCRBY命令,这样不需要知道这个键的值。TS.DECRBY功能相反。TS.GET命令可以获取最后一个值。使用TS.ALTER命令可以更改已创建键的元数据,包括字段和保留值等等。TS.MRANGE和TS.MGET是很有趣的命令,但解释起来有点复杂。RedisTimeSeries可以跟踪Redis数据库中所有的时间序列键。TS.MRANGE允许您可以给指定的键/值对打标签。因此,在我们的菜园的示例中,您可以获得4号温室的温度数据,然后使用TS.MRANGE查看整个Redis键空间中的不同键。还有很多类似的场景,TS.MGET使您可以通过标签获取某个最新的值。您也可以将RedisTimeSeries与其他系统一同使用,例如Prometheus和Grafana,这样是一种提高监控面板展示速度的好方法。

缓存中的RedisTimeSeries

即使我们发现客户在越来越多的场景中使用时间序列数据,还有许多公司仍将他们的时间序列类型的数据存储在关系数据库中。从技术架构的角度来看,这样做不适合需要扩展的大规模系统。当只有一两个人在刷新监控界面查看数据时,系统可能不会出问题,但是当您希望整个公司里的成千上万的都可以同时查看一个监控数据时,关系数据库的性能就会成为瓶颈。这就是我们为什么使用RedisTimeSeries来缓存时间序列数据,而不是将这些数据保存在较慢的数据库中。并且使用Redis还有其他好处,可以通过配置很方便的进行数据持久化。

往期精华

Redis干货分享

解读会话存储模式

为什么Redis6只支持RESP3?

利用Redis来优化功能部署和错误分类

Redis4.0鲜为人知的功能将加速您的应用程序

如何将Redis用于微服务通信的事件存储

RedisAI第一步

中间件小哥

中间件技术、IT咨询的快递小哥

点击“阅读原文”可查看英文原文。

预览时标签不可点收录于话题#个上一篇下一篇
1
查看完整版本: Redis干货分享在Redis中解锁