Redis学习笔记——如何在Redis中保存时间序列数据?

Posted qq_34132502

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis学习笔记——如何在Redis中保存时间序列数据?相关的知识,希望对你有一定的参考价值。

时间序列数据的特点

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

写特点

在实际应用中,时间序列数据通常是持续高并发写入的,例如,需要连续记录数万个设备的实时状态值。同时,时间序列数据的写入主要就是插入新数据,而不是更新一个已存在的数据,也就是说,一个时间序列数据被记录后通常就不会变了,因为它就代表了一个设备在某个时刻的状态值

这种数据的写入特点很简单,就是插入数据快,这就要求我们选择的数据类型,在进行数据插入时,复杂度要低,尽量不要阻塞

我们可以想到使用string或hash来保存。因为他们插入的时间复杂度都是 O ( 1 ) O(1) O(1)。但是需要注意,string不适合记录大量小数据

读特点

我们在查询时间序列数据时,既有对单条记录的查询,也有对某个时间范围内的数据的查询。

除此之外,还有一些更复杂的查询,比如对某个时间范围内的数据做聚合计算。如计算均值、最大 / 最小值、求和等。例如,我们要计算某个时间段内的设备压力的最大值,来判断是否有故障发生。

那用一个词概括时间序列数据的“读”,就是查询模式多。

如何保存时间序列数据

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

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

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

关于 Hash 类型,我们都知道,它有一个特点是,可以实现对单键的快速查询。这就满足了时间序列数据的单键查询需求。我们可以把时间戳作为 Hash 集合的 key,把记录的设备状态值作为 Hash 集合的 value。

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

虽然时间序列数据是按时间递增顺序插入 Hash 集合中的,但 Hash 类型的底层结构是哈希表,并没有对数据进行有序索引。

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

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

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

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

那 Redis 是怎么保证原子性操作的呢?这里就涉及到了 Redis 用来实现简单的事务的 MULTI 和 EXEC 命令。

  • MULTI 命令:表示一系列原子性操作的开始。收到这个命令后,Redis 就知道,接下来再收到的命令需要放到一个内部队列中,后续一起执行,保证原子性。
  • EXEC 命令:表示一系列原子性操作的结束。一旦 Redis 收到了这个命令,就表示所有要保证原子性的命令操作都已经发送完成了。此时,Redis 开始执行刚才放到内部队列中的所有命令操作。
    在这里插入图片描述如何对时间序列进行聚合计算

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

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

所以,如果我们只需要进行单个时间点查询或是对某个时间范围查询的话,适合使用 Hash 和 Sorted Set 的组合,它们都是 Redis 的内在数据结构,性能好,稳定性高。但是,如果我们需要进行大量的聚合计算,同时网络带宽条件不是太好时,Hash 和 Sorted Set 的组合就不太适合了。此时,使用 RedisTimeSeries 就更加合适一些。

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

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

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

  • 用 TS.CREATE 命令创建时间序列数据集合;
  • 用 TS.ADD 命令插入数据;
  • 用 TS.GET 命令读取最新数据;
  • 用 TS.MGET 命令按标签过滤查询数据集合;
  • 用 TS.RANGE 支持聚合计算的范围查询。

TS.CREATE

我们需要设置时间序列数据集合的 key 和数据的过期时间(以毫秒为单位)。此外,我们还可以为数据集合设置标签,来表示数据集合的属性。到达过期时间后,数据会自动删除

TS.MGET

在保存多个设备的时间序列数据时,我们通常会把不同设备的数据保存到不同集合中。此时,我们就可以使用 TS.MGET 命令,按照标签查询部分集合中的最新数据。

在使用 TS.CREATE 创建数据集合时,我们可以给集合设置标签属性。当我们进行查询时,就可以在查询条件中对集合标签属性进行匹配,最后的查询结果里只返回匹配上的集合中的最新数据。

TS.RANGE

在对时间序列数据进行聚合计算时,我们可以使用 TS.RANGE 命令指定要查询的数据的时间范围,同时用 AGGREGATION 参数指定要执行的聚合计算类型。

RedisTimeSeries 支持的聚合计算类型很丰富,包括求均值(avg)、求最大 / 最小值(max/min),求和(sum)等。

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"

以上是关于Redis学习笔记——如何在Redis中保存时间序列数据?的主要内容,如果未能解决你的问题,请参考以下文章

Redis学习笔记28——Pika:如何基于SSD实现大容量Redis

Redis学习笔记28——Pika:如何基于SSD实现大容量Redis

Redis 学习笔记

Redis学习笔记之入门基础知识——其他特性

Redis 学习笔记

Redis学习笔记之一:redis简介