什么是时间序列数据(Time Series Data,TSD,以下简称时序)从定义上来说,就是一串按时间维度索引的数据。简单的说,就是这类数据描述了某个被测量的主体在一个时间范围内的每个时间点上的测量值。它普遍存在于IT基础设施、运维监控系统和物联网中。
对时序数据进行建模的话,会包含三个重要部分,分别是:主体,时间点和测量值。时序数据从时间维度上将孤立的观测值连成一条线,从而揭示软硬件系统的状态变化。孤立的观测值不能叫时序数据,但如果把大量的观测值用时间线串起来,我们就可以研究和分析观测值的趋势及规律。
二、时序数据特征
数据写入:数据持续高速生成,持续高并发写入,数据点一旦插入数据库,就不会发生更改,可设置过期时间。
非结构化标签:时序数据通常是由许多来源在很长一段时间内连续产生的。例如,在IoT用例中,每个传感器都是时序数据的来源。在这种情况下,序列中的每个数据点都将源信息和其他传感器测量结果存储为标签。来自每个来源的数据标签可能不符合相同的结构或顺序。
数据价值递减:将来只有适当时间范围内的汇总数据摘要才有意义,存在明显的冷热数据,一般只会频繁查询近期数据。
三、Redis 时序数据方案
Redis是现在最受欢迎的NoSQL数据库之一,redis 内置了多种常用数据结构,适用于多种应用场景:缓存、队列、分布式锁、排行榜等等。除此之外redis 还可以自定义扩展模块引入更多数据结构, 比如引入RediSearch 模块用于redis 支持全文检索( 了解更多有用的模块可访问
https://redis.io/modules
)。以下主要讲述redis在时序数据存储的应用方案:
3.1 使用
Sorted Set
存储
SortedSets 是redis 内置的数据结构,跟set相比支持按权重值存储数据,支持权重的范围查询。时序数据的场景下,可将数据时间戳作为权重值存储,key存储具体的度量维度。
1、SortedSets 不是一种节约内存的数据结构
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
模块安装
拉取docker 镜像redislabs/redistimeseries,启动容器可在容器中找到 redistimeseries.so文件
loadmodule /path/to/redistimeseries.so
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)解决make的版本低不能使用,升级make
wget http://mirrors.ustc.edu.cn/gnu/make/make-4.3.tar.gz
./configure --prefix=/usr/local/make
# 此时的 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)。
TS.CREATE temperature:2:32 RETENTION 60000 DUPLICATE_POLICY MAX LABELS sensor_id 2 area_id 32 |
3、
TS.
M
ADD
:给某个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
|
5、
TS.CREA
T
ERULE
:
创建
数据
压缩规则
命令格式:
TS.CREATERULE sourceKey destKey AGGREGATION aggregationType timeBucket |
- 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 |
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: 过滤结果中最大值或最小值
- AGGREGATION:搭配聚合函数生成聚合桶bucket
- timeBucket: 聚合桶的时间范围(单位毫秒)
示例命令:
127.0.0.1:6379> TS.RANGE temperature:3:32 1548149180000 1548149210000 AGGREGATION avg 5000
1) 1) (integer) 1548149180000
2) 1) (integer) 1548149185000
3) 1) (integer) 1548149190000
|
8、
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... |
- WITHLABELS:
包含表示时间序列元数据标签的标签-值对
示例命令:
#过滤area_id=32的所有key 的最新数据
127.0.0.1:6379> TS.MGET FILTER area_id=32
3) 1) (integer) 1548149181000
3) 1) (integer) 1548149181000
|
3.3.3客户端库
优点
:
1、 快速提取数据:
作为一个内存数据库,RedisTimeSeries每秒可以在标准节点上提取超过500,000条记录。测试表明,使用16个Redis分片集群,每秒可以摄取超过1150万条记录。
2、工具丰富:
具有基本的时间序列功能,支持多种聚合。
3、资源效率:
支持定期采样数据,写入另外的集合,方便分粒度存储数据。例如,如果一天收集了超过十亿个数据点,则可以每分钟汇总一次数据以便对数据进行下采样,从而将数据集的大小减小为24 * 60 = 1,440个数据点。
缺点:
2、点查询只能获取最新数据,不支持直接取某一轮的数据
3、redis 是基于内存的,不适合存储太长时间的历史数据,当然除非有足够多的内存。
3.3.4选型建议
目前市面上也有一些专门的时序数据库,如influxDB,不过influxDB 集群版是收费的,单机版计算能力受限。如不想引入额外的架构,通过Redis RedisTimeSeries
扩展模块可以用来存储最近一段热点数据,历史数据可考虑压缩采样以更大时间粒度存储数据,亦可以搭配其他数据库存储原始数据。