一、时序数据介绍

什么是时间序列数据(Time Series Data,TSD,以下简称时序)从定义上来说,就是一串按时间维度索引的数据。简单的说,就是这类数据描述了某个被测量的主体在一个时间范围内的每个时间点上的测量值。它普遍存在于IT基础设施、运维监控系统和物联网中。

对时序数据进行建模的话,会包含三个重要部分,分别是:主体,时间点和测量值。时序数据从时间维度上将孤立的观测值连成一条线,从而揭示软硬件系统的状态变化。孤立的观测值不能叫时序数据,但如果把大量的观测值用时间线串起来,我们就可以研究和分析观测值的趋势及规律。

二、时序数据特征

数据写入:数据持续高速生成,持续高并发写入,数据点一旦插入数据库,就不会发生更改,可设置过期时间。

非结构化标签:时序数据通常是由许多来源在很长一段时间内连续产生的。例如,在IoT用例中,每个传感器都是时序数据的来源。在这种情况下,序列中的每个数据点都将源信息和其他传感器测量结果存储为标签。来自每个来源的数据标签可能不符合相同的结构或顺序。

数据价值递减:将来只有适当时间范围内的汇总数据摘要才有意义,存在明显的冷热数据,一般只会频繁查询近期数据。

三、Redis 时序数据方案

Redis是现在最受欢迎的NoSQL数据库之一,redis 内置了多种常用数据结构,适用于多种应用场景:缓存、队列、分布式锁、排行榜等等。除此之外redis 还可以自定义扩展模块引入更多数据结构, 比如引入RediSearch 模块用于redis 支持全文检索( 了解更多有用的模块可访问 Redis)。以下主要讲述redis在时序数据存储的应用方案:

3.1 使用Sorted Set存储

SortedSets 是redis 内置的数据结构,跟set相比支持按权重值存储数据,支持权重的范围查询。时序数据的场景下,可将数据时间戳作为权重值存储,key存储具体的度量维度。

缺点:

1、SortedSets 不是一种节约内存的数据结构

2、写入性能不高

3、内置缺少聚合工具,只支持客户端程序聚合,造成代码繁琐且网络IO占用高

3.2 使用Stream 存储

Redis Stream 是 Redis 5.0 版本新增加的数据结构,使用 Rax(Radix树的单独实现)实现,与 Sorted Sets 相比,Redis Streams 增强了插入和读取的性能。但 Stream 主要用于消息队列,仍然缺少了特定于时间序列的聚合工具。

缺点:

内置缺少聚合工具。

3.3 使用RedisTimeSeries存储

RedisTimeSeries 是专门为Redis 存取时序数据而设计的扩展模块。由于 RedisTimeSeries 不属于 Redis 的内置数据结构,在使用时,需要先把它的源码单独编译成动态链接库 redistimeseries.so。

3.3.1模块安装

步骤1:生成动态库

下载源码: git clone --recursive https://github.com/RedisTimeSeries/RedisTimeSeries.git

linux执行命令编译:

cd RedisTimeSeries
make setup

如果编译不成功,有个捷径获取动态库:

拉取docker 镜像redislabs/redistimeseries,启动容器可在容器中找到 redistimeseries.so文件

步骤2:加载动态文件到redis

在 redis.conf 文件中加入

loadmodule /path/to/redistimeseries.so

在启动的 client 中输入

module load /path/to/redistimeseries.so

在服务器启动是加载

redis-server --loadmodule /path/to/redistimeseries.so

步骤3:查看timeseries模块是否安装成功

root@1738e25ad4eb:/data# redis-cli
127.0.0.1:6379> module list
1) 1) "name"
   2) "timeseries"
   3) "ver"
   4) (integer) 10608

遇到的问题与解决方案:

(1)解决make的版本低不能使用,升级make

wget http://mirrors.ustc.edu.cn/gnu/make/make-4.3.tar.gz
tar xf make-4.3.tar.gz
cd make-4.3/
# 安装到指定目录
./configure  --prefix=/usr/local/make
make && make install
make -v
# 此时的 make 还是3.82 与环境变量有关系,可执行下面操作
mv /usr/bin/make /usr/lib/make.old
ln -s /usr/local/make/bin/make /usr/bin/make

(2)解决报错 libssl.so.1.1: cannot open shared object file: No such file or directory

(3)解决libc.so.6版本问题 /lib64/libc.so.6:version 'GLIBC_XXX' not found

3.3.2 主要命令

以下命令基本可以实现时序数据存储与查询:

1、TS.CREATE创建一个时序数据集合

命令格式:

TS.CREATE key [RETENTION retentionTime] [ENCODING [UNCOMPRESSED|COMPRESSED]] [CHUNK_SIZE size] [DUPLICATE_POLICY policy] [LABELS label value..]

参数说明:

- RETENTION: 与最后的事件时间相比样本的最大年龄(以毫秒为单位),默认值为0,代表数据不会过期

- ENCODING: 编码模式COMPRESSED使用压缩算法存储 ,UNCOMPRESSED将原始样本保存在内存中

- CHUNK_SIZE: 数据分配的内存大小,单位字节,必须是 8 的倍数,默认值:4096。

- DUPLICATE_POLICY: 重复样本策略配置。

- LABELS: 标签是key的元数据,以键值对方式存储

示例命令:

TS.CREATE temperature:2:32 RETENTION 60000 DUPLICATE_POLICY MAX LABELS sensor_id 2 area_id 32


2、TS.ADD:给某个key 新增时序数据,如果不存在则会创建集合

命令格式:

TS.CREATE key [RETENTION retentionTime] [ENCODING [UNCOMPRESSED|COMPRESSED]] [CHUNK_SIZE size] [DUPLICATE_POLICY policy] [LABELS label value..]

复杂度:

如果在时间序列上存在压缩规则,则 TS.ADD性能可能会降低。TS.ADD当 M 是压缩规则的数量或没有压缩的 O(1) 时 ,复杂度 总是 O(M)。

参数说明:

- timestamps: 时间戳,单位毫秒

- value:  double 类型的数值

其他参数是可选的,与TS.CREATE 一致

示例命令:

TS.CREATE temperature:2:32 RETENTION 60000 DUPLICATE_POLICY MAX LABELS sensor_id 2 area_id 32

3、TS.MADD:给某个key 批量添加数据

命令格式:

TS.MADD key timestamp value [key timestamp value ...]

示例命令:

127.0.0.1:6379>TS.MADD temperature:2:32 1548149180000 26 cpu:2:32 1548149183000 54
1) (integer) 1548149180000
2) (integer) 1548149183000
127.0.0.1:6379>TS.MADD temperature:2:32 1548149181000 45 cpu:2:32 1548149180000 30
1) (integer) 1548149181000
2) (integer) 1548149180000

4、TS.DEL删除某个key 某个时间范围的样本数据

命令格式:

TS.DEL key fromTimestamp toTimestamp

返回值:被移除的样本数

示例命令:

127.0.0.1:6379>TS.DEL temperature:2:32 1548149180000 1548149183000
(integer) 150

5、TS.CREATERULE:创建数据压缩规则

命令格式:

TS.CREATERULE sourceKey destKey AGGREGATION aggregationType timeBucket

参数说明:

- sourceKey : 源时间序列的键名

- destKey : 目标时间序列的键名

- aggregationType : 聚合类型avg、sum、min、max、range、count、first、last、std.p、std.s、var.p、var.s

- timeBucket : 以毫秒为单位的聚合时间桶

6、TS.DELETERULE删除压缩规则

命令格式:

TS.DELETERULE sourceKey destKey

参数说明:

- sourceKey : 源时间序列的键名

- destKey : 目标时间序列的键名

7、TS.RANGE范围查询,可配合聚合函数使用, 需要指定key

命令格式:

TS.RANGE key fromTimestamp toTimestamp
         [FILTER_BY_TS TS1 TS2 ..]
         [FILTER_BY_VALUE min max]
         [COUNT count] [ALIGN value]
         [AGGREGATION aggregationType timeBucket]

参数说明:

- FILTER_BY_TS: 返回特定时间戳的数据

- FILTER_BY_VALUE: 过滤结果中最大值或最小值

- COUNT:返回的结果的最大数量

- AGGREGATION:搭配聚合函数生成聚合桶bucket

- timeBucket: 聚合桶的时间范围(单位毫秒)

示例命令:

127.0.0.1:6379> TS.RANGE temperature:3:32 1548149180000 1548149210000 AGGREGATION avg 5000
1) 1) (integer) 1548149180000
   2) "26.199999999999999"
2) 1) (integer) 1548149185000
   2) "27.399999999999999"
3) 1) (integer) 1548149190000
   2) "24.800000000000001"

8、TS.GET获取指定key的新一条数据

命令格式:

TS.GET key

复杂度:

TS.GET 复杂度为 O(1)

示例命令:

127.0.0.1:6379> TS.GET temperature:2:32
1) (integer) 1548149279
2) "23"

9、TS.MGET:获取与特定过滤器匹配的所有key的最新一条数据。

命令格式:

TS.MGET [WITHLABELS | SELECTED_LABELS label1 ..] FILTER filter...

参数说明:

- FILTER: 过滤器

- WITHLABELS: 包含表示时间序列元数据标签的标签-值对

复杂度:

TS.MGET 复杂度为 O(n)。

示例命令:


#过滤area_id=32的所有key 的最新数据
127.0.0.1:6379> TS.MGET FILTER area_id=32
1) 1) "temperature:2:32"
    2) (empty list or set)
    3) 1) (integer) 1548149181000
       2) "30"
2) 1) "temperature:3:32"
    2) (empty list or set)
    3) 1) (integer) 1548149181000
        2) "29"

3.3.3客户端库 

Project

Language

License

Author

JRedisTimeSeries

Java

BSD-3

Redis

redis-modules-java

Java

Apache-2

dengliming

redistimeseries-go

Go

Apache-2

Redis

redis-py

Python

MIT

Redis

NRedisTimeSeries

.NET

BSD-3

Redis

phpRedisTimeSeries

PHP

MIT

Alessandro Balasco

redis-time-series

JavaScript

MIT

Rafa Campoy

redistimeseries-js

JavaScript

MIT

Milos Nikolovski

redis-modules-sdk

Typescript

BSD-3-Clause

Dani Tseitlin

redis_ts

Rust

BSD-3

Thomas Profelt

redistimeseries

Ruby

MIT

Eaden McKee

redis-time-series

Ruby

MIT

Matt Duszynski

优点:

1、 快速提取数据:作为一个内存数据库,RedisTimeSeries每秒可以在标准节点上提取超过500,000条记录。测试表明,使用16个Redis分片集群,每秒可以摄取超过1150万条记录。

2、工具丰富:具有基本的时间序列功能,支持多种聚合。

3、资源效率:支持定期采样数据,写入另外的集合,方便分粒度存储数据。例如,如果一天收集了超过十亿个数据点,则可以每分钟汇总一次数据以便对数据进行下采样,从而将数据集的大小减小为24 * 60 = 1,440个数据点。

缺点:

1、时序值只能存double类型,不能存字符

2、点查询只能获取最新数据,不支持直接取某一轮的数据

3、redis 是基于内存的,不适合存储太长时间的历史数据,当然除非有足够多的内存。

3.3.4选型建议

目前市面上也有一些专门的时序数据库,如influxDB,不过influxDB 集群版是收费的,单机版计算能力受限。如不想引入额外的架构,通过Redis RedisTimeSeries 扩展模块可以用来存储最近一段热点数据,历史数据可考虑压缩采样以更大时间粒度存储数据,亦可以搭配其他数据库存储原始数据。