dot 快速未来的到来,在您所在的城市举办的活动中。

加入我们,参加 Redis 发布会

使用 Redis 解锁时间序列数据

(注意:这篇博文改编自我在 6 月份发表的网络研讨会。要更深入地了解 RedisTimeSeries,立即注册并观看网络研讨会!)

大多数开发人员都知道 Redis 的实时响应能力使其非常适合处理时间序列数据。但是,究竟什么是时间序列数据?许多定义会扩展到一页又一页的解释,但我认为它可以显著简化:

基本上,时间序列数据是将时间编码为索引的数据,并且每个记录的时间都有一个数值。如果您将它可视化为两列,一列将具有某种时间索引,通常是 Unix 纪元形式的时间戳。另一列将具有某种数值。

非常简单。

至关重要的是,您可以使用时间边界分析时间序列数据,例如,查看 1 月 1 日到 1 月 3 日之间发生了什么。您也可以详细到秒,有时甚至毫秒。您还可以将数据分成时间单位,以查看每小时发生了什么。然后,如果您不想查看时间序列数据中的每个事件,您可以在其上添加聚合,例如,获取每小时的平均值。

许多人在想到时间序列数据时会想到股票图表。这是一种查看股票在给定时间段内的表现的好方法。我经常查看的一个时间序列数据用例是服务器在任何指定时间间隔内的 CPU 负载。时间序列数据也是查看传感器数据和其他物联网 (IoT) 信息的好方法。无论何时查看随时间推移的趋势,通常都是从某种时间序列数据库或时间序列结构中获取的。

Redis 和时间序列的历史

现在让我们关注 Redis 和时间序列。这一切都始于有序集合,这是 Redis 中的内置数据结构之一。人们很早就开始使用有序集合来处理时间序列数据,看起来像这样

> ZADD mySortedSet 1559938522 1000

此示例包含ZADD命令,mySortedSets作为键,以及时间戳作为分数。最后是成员,它是值。

这很好,但是您只能获得范围,您不能进行平均或降采样。

集合不能重复。在这里,如果您有两个具有相同值的不同时间戳,则集合将基于成员(在本例中,我们将其定义为值)。因此,在下面的示例中,第二个实际上将是一个更新操作 - 它将覆盖第一个。这对于时间序列数据不起作用,人们在以这种方式使用它时会遇到一些糟糕的体验:

> ZADD mySortedSet 1559938522 1000
> ZADD mySortedSet 1559938534 1000

开发人员想出了许多计算复杂且难以实现的解决方法。一定有更简单的方法。

进入 Redis 流

大约两年前,Redis 4.0发布了 Redis 流,它旨在解决使用统一日志架构和进程间消息传递构建应用程序的问题。

Redis 流在时间序列用例中比有序集合具有重要优势。它允许自动生成 ID、没有重复项以及每个样本的字段/值对。

> XADD myStream * myValue 1000

> XADD myStream * myValue 1000 anotherField hello

如您在第一个命令中看到的,我们将字段myField设置为1000。在第二个命令中,创建了一个新条目,其中myValue也设置为1000,以及anotherField设置为hello。这些都是位于键myStream处的流中的条目。

但这仍然缺乏重要的功能,并且实际上不是为时间序列数据设计的。您可以轻松地获取时间范围,但其他功能不多。

现在让我们稍微倒退一下,谈谈 Redis 模块 API,它比流稍微早一点出现,并允许 Redis 拥有额外的社区和数据类型。Redis 用户可以构建充当 Redis 内部一等公民的模块。现有模块包括从RediSearchRedisGraphRedisJSON。现在还有RedisTimeSeries,它基本上在 Redis 内部创建了一个完整的时序数据库。

RedisTimeSeries 模块的工作原理

在我们开始使用RedisTimeSeries 模块之前,了解幕后发生的事情很重要。

您需要了解的第一件事是“块”。您实际上从未直接操作过块,但 RedisTimeSeries 将所有数据存储在这些块中。每个块包含一个双向链表中的两个相关数组(一个用于时间戳,一个用于样本值)。

例如,假设我想将时间戳放入我的时间序列数据库中。它会放在两个数组中的第一行。如果您有其他样本,它们只会进入数组。

块是固定大小的。当块已满时,其他数据会自动进入下一个块。在链表的开头或结尾添加是计算上微不足道的,因此当添加新块时,它非常轻量级。

但与大多数 Redis 数据类型不同,最好先创建时间序列键。在这种情况下,我的命令是TS.CREATE。然后我有myTS,这是我在这里使用的键。

因此,假设我们要为该键添加一些元数据。假设我们正在经营一家蔬菜苗圃,并且想要跟踪温室 4 中的卷心菜编号 47;我们将此元数据称为标签。这将适用于整个时间序列中的每个样本。

使用时间序列数据时另一个重要部分是保留。假设我们不关心 60 秒以上的数据。RedisTimeSeries 可以修剪掉您指定保留时间段之外的内容。

我们可以使用名为TS.ADD的操作添加值。第一个参数是键myTS,星号是 Redis 流借用的语法,表示 Redis 将自动生成时间戳。在本例中,值为 834。

让我们添加另一个样本,并指定时间戳。请注意,时间戳实际上是追加的,因此您不能进入并添加超过最近使用的时间戳的内容。后续的TS.ADD必须是大于该值的时间戳。

接下来,要获取有界的结果,您将请求两个时间戳之间的所有样本。使用我们的示例,您可以看到第一个时间戳的值为 834,第二个值为 1000。

这很有用,但是您可能希望获取每 30 秒时间段的平均值。这里,avg是我们使用的关键字,当然,917 是 834 和 1,000 的平均值。

但是,当您有更多数据时会发生什么?您可能不想一直运行TS.RANGE命令,只想以粒度方式提取该数据。

好的,我们可以创建规则! myTS 是我的键:这是源。目标是 myTS2,这是第二个键。这里的所有块代表 30 秒的时间,RedisTimeSeries 将自动将其放入目标键的二级键中。因此,每过 30 秒,您将获得一个添加到 myTS2 的样本。

但等等,还有更多!它不仅仅限于平均值。您可以求和,可以获取最小值,可以获取最大值,可以获取范围。您可以获取计数(多少)以及第一个或最后一个。所有这些不同的聚合函数也适用于 TS RANGE

更多 RedisTimeSeries 命令

让我们看看 RedisTimeSeries 还能做什么。命令 TS.INCRBYTS.DECRBY 用于随着时间的推移进行计数。 TS.INCRBY 将先前条目增加某个值。假设您知道在 10 秒内收集了 10 个小部件。您将在键上运行 TS.INCRBY。这样,您就不必知道先前值,并且可以进行持续计数。对于 TS.DECRBY 也是如此,只是反过来。

TS.GET 同时获取最后一个值。而 TS.ALTER 允许您更改已创建键的元数据,包括字段、值保留时间等。

TS.MRANGETS.MGET 很有趣,但有点难解释。RedisTimeSeries 跟踪数据库中所有不同的时间序列键。 TS.MRANGE 允许您指定标签的键/值对。因此,在我们的温室示例中,您可以获取温室 4 的温度读数,然后使用 TS.MRANGE 查看整个键空间中的不同键。类似地, TS.MGET 允许您通过标签获取单个最新的值。您可以将 RedisTimeSeries 与基础设施的不同部分连接起来,例如 Prometheus 和 Grafana,这是一种为监控仪表板提供支持的好方法。

缓存 RedisTimeSeries

即使我们发现我们的客户在越来越多的用例中使用时间序列数据,许多公司仍然将时间序列类型存储在关系数据库中。从技术角度来看,在扩展事物方面,这根本不是一个很好的选择。当只有两三个人查看仪表板时,它可能工作得很好,但是当您希望整个组织中的数千人查看相同的分析仪表板时,关系数据库的临时查询通常跟不上。

这就是我们看到 RedisTimeSeries 被用来缓存原本将在较慢的数据库中使用的时间序列数据的原因,以及为了获得其他 Redis 优势,包括选择是持久化您的数据还是将其保持为短暂数据。

想了解更多?