14 | 如何在Redis中保存时间序列数据?


Redis核心技术与实战

实践篇

14 | 如何在Redis中保存时间序列数据?

与发生时间相关的一组数据,就是时间序列数据。 这些数据的特点是没有严格的关系模型,记录的信息可以表示成键和值的关系(例如,一个设备 ID 对应一条记录),所以,并不需要专门用关系型数据库(例如 MySQL)来保存。而 Redis 的键值数据模型,正好可以满足这里的数据存取需求。

时间序列数据的读写特点
  • 写的特点
    在实际应用中,时间序列数据通常是持续高并发写入。同时,时间序列数据的写入主要就是插入新数据,而不是更新一个已存在的数据,也就是说,一个时间序列数据被记录后通常就不会变了,因为它就代表了一个设备在某个时刻的状态值。
    简单地说,就是插入数据快,所以选择的数据类型,在进行数据插入时,复杂度要低,尽量不要阻塞。(String 类型在记录小数据时,元数据的内存开销比较大,不适合保存大量数据。)
  • 读的特点
    在查询时间序列数据时,既有对单条记录的查询,也有对某个时间范围内的数据的查询。除此之外,还有一些更复杂的查询,比如对某个时间范围内的数据做聚合计算。这里的聚合计算,就是对符合查询条件的所有数据做计算,包括计算均值、最大 / 最小值、求和等。
    简单地说,就是查询模式多

针对时间序列数据的“写要快”,Redis 的高性能写特性直接就可以满足了;而针对“查询模式多”,也就是要支持单点查询、范围查询和聚合计算,Redis 提供了保存时间序列数据的两种方案,分别可以基于 Hash 和 Sorted Set 实现,以及基于 RedisTimeSeries 模块实现

基于 Hash 和 Sorted Set 保存时间序列数据

Hash 和 Sorted Set 组合的方式有一个明显的好处:它们是 Redis 内在的数据类型,代码成熟和性能稳定。所以,基于这两个数据类型保存时间序列数据,系统稳定性是可以预期的。

疑问:为什么保存时间序列数据,要同时使用这两种类型?

Hash 类型,可以实现对单键的快速查询。可以把时间戳作为 Hash 集合的 key,把记录的设备状态值作为 Hash 集合的 value。

在这里插入图片描述

查询某个时间点或者是多个时间点上的温度数据时,直接使用 HGET 命令或者 HMGET 命令,就可以分别获得 Hash 集合中的一个 key 和多个 key 的 value 值。

HGET device:temperature 202008030905
"25.1"

HMGET device:temperature 202008030905 202008030907 202008030908
1) "25.1"
2) "25.9"
3) "24.9"

Hash 类型有个短板:它并不支持对数据进行范围查询。

为了能同时支持按时间戳范围的查询,可以用 Sorted Set 来保存时间序列数据,因为它能够根据元素的权重分数来排序。可以把时间戳作为 Sorted Set 集合的元素分数,把时间点上记录的数据作为元素本身。

以保存设备温度的时间序列数据为例,如下图所示:

在这里插入图片描述

使用 Sorted Set 保存数据后,就可以使用 ZRANGEBYSCORE 命令,按照输入的最大时间戳和最小时间戳来查询这个时间范围内的温度值。

ZRANGEBYSCORE device:temperature 202008030907 202008030910
1) "25.9"
2) "24.9"
3) "25.3"
4) "25.2"

同时使用 Hash 和 Sorted Set,可以满足单个时间点和一个时间范围内的数据查询需求。

疑问:如何保证写入 Hash 和 Sorted Set 是一个原子性的操作?

只有保证了写操作的原子性,才能保证同一个时间序列数据,在 Hash 和 Sorted Set 中,要么都保存了,要么都没保存。否则,就可能出现 Hash 集合中有时间序列数据,而 Sorted Set 中没有,那么,在进行范围查询时,就没有办法满足查询需求。

Redis 用 MULTI 和 EXEC 命令来实现简单的事务。(Redis不具备原子性,MULTI 和 EXEC 命令之间的操作若没有报错,则不会保证原子性)

  • MULTI 命令:表示一系列原子性操作的开始。收到这个命令后,Redis 就知道,接下来再收到的命令需要放到一个内部队列中,后续一起执行,保证原子性。
  • EXEC 命令:表示一系列原子性操作的结束。一旦 Redis 收到了这个命令,就表示所有要保证原子性的命令操作都已经发送完成了。此时,Redis 开始执行刚才放到内部队列中的所有命令操作。

在这里插入图片描述

以保存设备状态信息的需求为例,把设备在 2020 年 8 月 3 日 9 时 5 分的温度,分别用 HSET 命令和 ZADD 命令写入 Hash 集合和 Sorted Set 集合。

127.0.0.1:6379> MULTI
OK

127.0.0.1:6379> HSET device:temperature 202008030911 26.8
QUEUED

127.0.0.1:6379> ZADD device:temperature 202008030911 26.8
QUEUED

127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 1

疑问:如何对时间序列数据进行聚合计算?

聚合计算一般被用来周期性地统计时间窗口内的数据汇总状态,在实时监控与预警等场景下会频繁执行。

因为 Sorted Set 只支持范围查询,无法直接进行聚合计算,所以只能先把时间范围内的数据取回到客户端,然后在客户端自行完成聚合计算。这个方法虽然能完成聚合计算,但是会带来一定的潜在风险,也就是大量数据在 Redis 实例和客户端间频繁传输,这会和其他操作命令竞争网络资源,导致其他操作变慢。

为了避免客户端和 Redis 实例间频繁的大量数据传输,可以使用 RedisTimeSeries 来保存时间序列数据。

基于 RedisTimeSeries 模块保存时间序列数据

RedisTimeSeries 是 Redis 的一个扩展模块。它专门面向时间序列数据提供了数据类型和访问接口,并且支持在 Redis 实例上直接对数据进行按时间范围的聚合计算

因为 RedisTimeSeries 不属于 Redis 的内建功能模块,在使用时,需要先把它的源码单独编译成动态链接库 redistimeseries.so,再使用 loadmodule 命令进行加载,如下所示:

loadmodule redistimeseries.so

当用于时间序列数据存取时,RedisTimeSeries 的操作主要有 5 个:

1. 用 TS.CREATE 命令创建一个时间序列数据集合

创建一个 key 为 device:temperature、数据有效期为 600s 的时间序列数据集合。

TS.CREATE device:temperature RETENTION 600000 LABELS device_id 1
OK

2. 用 TS.ADD 命令插入数据,用 TS.GET 命令读取最新数据

TS.ADD device:temperature 1596416700 25.1
1596416700

TS.GET device:temperature 
25.1

3. 用 TS.MGET 命令按标签过滤查询数据集合

TS.MGET FILTER device_id!=2 
1) 1) "device:temperature:1"
   2) (empty list or set)
   3) 1) (integer) 1596417000
      2) "25.3"
2) 1) "device:temperature:3"
   2) (empty list or set)
   3) 1) (integer) 1596417000
      2) "29.5"
3) 1) "device:temperature:4"
   2) (empty list or set)
   3) 1) (integer) 1596417000
      2) "30.1"

4. 用 TS.RANGE 支持需要聚合计算的范围查询
在对时间序列数据进行聚合计算时,可以使用 TS.RANGE 命令指定要查询的数据的时间范围,同时用 AGGREGATION 参数指定要执行的聚合计算类型。RedisTimeSeries 支持的聚合计算类型很丰富,包括求均值(avg)、求最大 / 最小值(max/min),求和(sum)等。

按照每 180s 的时间窗口,对 2020 年 8 月 3 日 9 时 5 分和 2020 年 8 月 3 日 9 时 12 分这段时间内的数据进行均值计算:

TS.RANGE device:temperature 1596416700 1596417120 AGGREGATION avg 180000
1) 1) (integer) 1596416700
   2) "25.6"
2) 1) (integer) 1596416880
   2) "25.8"
3) 1) (integer) 1596417060
   2) "26.1"
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违の欢喜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值