今天我们很高兴的宣布 RedisTimeSeriesV1.0 发布(编者注:原文发布时间为 2019 年 6 月 27 日)。RedisTimeSeries 是由 Redis Labs 开发的 Redis 模块,用于增强使用 Redis 管理时间序列数据的体验。我们在六个月前发布了 RedisTimeSeries 的预览 / Beta 版本,并感谢我们在第一个 GA 版本上从社区和客户那里得到的所有好的反馈和建议。为了发布此版本,我们执行了一个基准测试,与 Redis 中的其他时间序列方法相比 RedisTimeSeries 每秒可实现 125K 查询。快速浏览完整的结果,或者花点时间先了解导致我们构建这个新模块的原因。
为什么选择 RedisTimeSeries?
许多 Redis 用户近十年来一直使用 Redis 处理时间序列数据,并且乐此不疲。正如我们稍后将解释的那样,这些开发人员使用的是 Redis 的通用的数据结构。因此,让我们先回过头来解释为什么我们决定构建一个具有专用时间序列数据结构的模块。在下面的数据库引擎趋势图中,可以看到时间序列数据库最近获得了最多的人气。除了用于自动驾驶汽车,算法交易,智能家居,在线零售等不断增长的数据量和新的时间序列使用场景之外,我们认为这种趋势有两个主要的技术原因。
第一个原因是时间序列数据的查询模式和规模不同于现有数据库技术构建的数据库。虽然大多数数据库设计时都考虑读的次数大于写的次数,但时间序列场景具有大量数据的高摄取率和较低数量的读取查询特点。在根因分析的案例中,读取是零星的,只触及数据集的随机部分。在训练 AI 模型的使用案例中(例如,对于传感器数据中的异常检测),通常读取将跨越数据集的更大部分但是少于写入的数量。由于 Redis 的可扩展架构可提供高写入吞吐量和低延迟,因此 Redis 非常适合当前的时间序列查询模式。
这种趋势的第二个原因是传统数据库技术中不存在使用时间序列所需的工具集。资源的有效使用需要许多数据变化,例如历史时间序列数据的自动下采样和双增量编码,以及用于直观查询和聚合时间序列数据的特征。传统的数据库技术在应用程序方面引入了大量工作来解决诸如保留、下采样和聚合等功能。
在 RedisLabs,我们坚持吃自己的“狗粮”。对于我们的云产品(管理在数千个 RedisEnterprise 集群上运行的 100 多万个 Redis 数据库),我们从内部 Redis 数据库中的每个集群收集指标。用于我们自己的核心架构内部项目中,我们亲身体验了使用基础 Redis 数据结构用于时间序列用例开发工作和限制条件。我们认为必须有一种更好,更有效的方法来实现。除了上面的工具集特定功能之外,我们还需要开箱即用的二级索引,以便我们可以有效地查询时间序列集。
使用 Redis 核心数据结构实现时间序列
重用现有数据结构来实现时间序列有两种方式:Sorted Sets 和 Streams。许多文章解释了如何使用 Redis 核心数据结构建模时间序列。以下是我们稍后将在基准测试中使用的一些关键原则。
SortedSets
SortedSets 按其权重值存储数据。在时间序列数据的情况下,权重值是观察事件的时间戳。该值重复时间戳,后跟分隔符和实际测量值,例如:“<时间戳>:<测量>”。这样做是因为有序集合中的每个值都应该是唯一的。或者,该值保存唯一键的名称,该键存储散列,其中可以为给定时间戳保留更多数据或测量值。
这种方法的缺点:
SortedSets 不是一种节约内存的数据结构
插入的时间复杂度是 O(log(N)),因此集群越大写入耗时越长
缺少时间序列工具集
Streams
Redis Streams 是最近添加的数据结构(因此目前不太常用于时间序列),它比 Sorted Sets 消耗更少的内存,并且使用 Rax(Radix 树的单独实现)实现。通常,与 Sorted Sets 相比,Redis Streams 增强了插入和读取的性能,但仍然缺少了特定于时间序列的工具集,因为它被设计为通用数据结构。
这种方法的缺点:
缺少时间序列工具集
RedisTimeSeries 功能
在 RedisTimeSeries 中,我们引入了一种新的数据类型,它使用固定大小的内存块作为时间序列样本,由与 Redis Streams 相同的 Radix Tree 实现索引。使用 Streams,您可以创建一个上限流,有效地限制消息数量。在 RedisTimeSeries 中,您可以以毫秒为单位应用保留策略。这对于时间序列用例更好,因为它们通常在给定时间窗口期间对数据感兴趣,而不是对固定数量的样本感兴趣。
数据下采样 / 压缩
如果要无限期保留所有原始数据点,则数据集将随时间线性增长。但是,如果您的用例允许您进一步缩短细粒度数据,则可以应用下采样。这允许您通过使用给定的聚合函数聚合给定时间窗口的原始数据来保留较少的历史数据点。RedisTimeSeries 支持使用以下聚合进行下采样:avg,sum,min,max,range,count,first 和 last。
二级索引
使用 Redis 的核心数据结构时,您只能通过保存时间序列的确切键来检索时间序列。不幸的是,对于许多时间序列场景(例如根因分析或监视),您的应用程序将不知道它正在寻找的确切键。这些场景通常希望查询一组在几个维度上相互关联的时间序列,以提取您需要信息。您可以使用核心 Redis 数据结构创建自己的二级索引来帮助解决此问题,但这会带来很高的开发成本,并且需要您管理边缘情况以确保索引正确。
RedisTimeSeries 根据您可以添加到每个时间序列的“字段值”对(a.k.a 标签)为您执行此索引,并用于在查询时进行筛选(我们的文档中提供了这些筛选器的完整列表)。以下是使用两个标签创建时间序列的示例(sensor_id 和 area_id 分别是值为 2 和 32 的字段)和保留窗口为 60,000 毫秒:
读取时的聚合
当您需要查询时间序列时,如果您只对给定时间间隔内的平均值感兴趣,则流式传输所有原始数据点非常麻烦。RedisTimeSeries 遵循 Redis 理念,仅传输最少的所需数据以确保最低延迟。以下是使用聚合函数的 5000 毫秒时间桶聚合查询示例:
集成
RedisTimeSeries 与现有的时间序列工具集成了多种功能。其中一个集成是我们用于 Prometheus 的 RedisTimeSeries 适配器,它将所有监控指标保留在 RedisTimeSeries 中,同时利用整个 Prometheus 生态系统。
此外,我们还为 Grafana 和 Telegraph 创建了直接集成。此存储库包含 RedisTimeSeries 的 docker-compose 设置,其远程写入适配器,Prometheus 和 Grafana。它还配备了一组数据生成器和预先构建的 Grafana 仪表板。
基准测试
为了展示我们新的 GA RedisTimeSeries 模块的全部功能,我们针对处理时间序列数据的三种常用技术进行了基准测试。我们使用具有两个独立机器的客户端 - 服务器设置来比较分类集,Streams 和 RedisTimeSeries 的性能,以便提取,查询时间和内存消耗。
特别是,我们的设置包括:
客户端和服务器:AWS c5.18xl
RedisEnterprise 版本 5.4.4-7
具有 16 个分片和 48 个代理线程的 RedisEnterprise 数据库
一个数据集:
4,000 台设备,每台设备 10 个指标
每个指标都是 CPU 测量
每公制 10,800 个样本(每 10 秒 30 小时)
共计 4320,000 个样本
数据建模方法
Redis Streams 允许您在给定时间戳的消息中添加多个字段值对。对于每个设备,我们收集了 10 个指标,这些指标在单个流消息中被建模为 10 个单独的字段。
对于 Sorted Sets,我们以两种不同的方式对数据建模。对于“按设备排序集”,我们将指标连接起来并用冒号分隔,例如“< timestamp>:< metric1>:< metric2>:…:< metric10>”。
当然,这会消耗更少的内存,但需要更多的 CPU 周期才能在读取时获得正确的度量标准。它还意味着改变每个设备的指标数量变得复杂,这就是为什么我们还对第二个排序集方法进行基准测试。在“按度量排序的集合”中,我们将每个度量标准保留在其自己的排序集中,并且每个设备有 10 个有序集。我们以“< timestamp>:< metric>”格式记录值。
另一种替代方法是通过创建具有唯一密钥的散列来标准化数据,以跟踪给定时间戳的给定设备的所有测量。此键将是排序集中的值。但是,必须访问许多哈希来读取时间序列会在读取时间内花费巨大的成本,因此我们放弃了这条路径。
在 RedisTimeSeries 中,每个时间序列都包含一个度量标准。我们选择这种设计来保持 Redis 原则,即大量小键比少量的大键更好。
值得注意的是,我们的基准测试没有利用 RedisTimeSeries 的开箱即用的二级索引功能。该模块在每个分片中保留一个部分二级索引,并且由于索引继承了它所索引的键的相同哈希槽,因此它始终托管在同一个分片上。这种方法会使原生数据结构的设置更加复杂,因此为了简单起见,我们决定不将它包含在我们的基准测试中。此外,虽然 Redis Enterprise 可以使用代理将 TS.MGET 和 TS.MRANGE 等命令的请求扇出到所有分片并聚合结果,但我们选择不在基准测试中利用这一优势。
数据摄取
对于我们基准测试的数据摄取部分,我们通过测量每秒可以摄取的设备数据来比较这四种方法。我们的客户端有 8 个工作线程,每个线程有 50 个连接,每个请求有 50 个命令。
我们所有的摄取操作都以亚毫秒的延迟执行,虽然两者都使用相同的 Rax 数据结构,但 RedisTimeSeries 方法的吞吐量略高于 Redis Streams。
可以看出,使用 Sorted Sets 的两种方法产生非常不同的吞吐量。这显示了针对特定用例进行原型设计的价值。正如我们将在读取性能上看到的那样,每个设备的 Sorted Sets 具有改进的写入吞吐量,但代价是查询性能。这是用于您对用例的摄取,查询性能和灵活性(请记住我们之前制作的数据建模注释)之间的权衡。
读取性能
我们在此基准测试中使用的查询单个时间序列来测试,测试中每个桶中占用的 CPU 百分比相同并在一个小时内聚合数据。我们在查询中考虑的时间范围恰好是一小时,因此返回了一个最大值。对于 RedisTimeSeries,这是开箱即用的功能(如前所述)。
对于 RedisStreams 和 Sorted Sets 方法,我们创建了以下 LUA 脚本。客户端拥有 8 个线程和 50 个连接。由于我们执行了相同的查询,因此只有一个分片被命中,并且在所有四种情况下,此分片最大值为 100% CPU。
在这里,您可以看到使用与其一起运行的工具箱为特定用例提供专用数据结构的真正强大功能。RedisTimeSeries 比其他方法更加高效,并且是唯一一个达到亚毫秒响应时间的方法。
内存利用率
在 RedisStreams 和 Sorted Set 方法中,样本都保存为字符串,而在 RedisTimeSeries 中则是浮点数。在这个特定的数据集中,我们选择了一个 CPU 进行测量,其舍入整数值在 0-100 之间,因此消耗了两个字节的内存。但是,在 RedisTimeSeries 中,每个度量标准都具有 64 位精度。
与 Sorted Set 方法相比,RedisTimeSeries 可以显着降低内存消耗。鉴于时间序列数据的无限性,这通常是评估的关键标准 - 需要在内存中保留的总体数据集大小。Redis Streams 进一步减少了内存消耗,但是当需要更多数字以获得更高精度时,它将等于或高于 RedisTimeSeries。
结论
在选择方法时,您需要了解时间序列用例的摄取率,查询工作负载,总体数据集大小和内存占用量。我们已知,有几种方法可以在 Redis 中建模时间序列数据,每种方法都有不同的特征。RedisTimeSeries 提供了一种新方法,将时间视为一等公民,并提供了如前所述的开箱即用的时间序列工具包。它结合了高效的内存使用和非凡的查询性能,在摄取过程中只需很小的开销。时间序列数据的实时分析的愿望得以实现。
下一步计划
我们对在 1.0 GA 版本的 RedisTimeSeries 中取得的成果感到满意,但这只是一个开始。我们很乐意听取您的反馈意见,并且将其纳入我们的路线图。与此同时,以下是我们接下来计划的事项列表:
时间戳/测量的 Double delta encoding 。这将显着减少每次测量所消耗的内存量,如果内存利用率很重要的话,RedisTimeSeries 将成为比 Redis Streams 更好的选择。
现有可视化库的更多集成,对于流提供程序的接收器以及 RDBTools 的本机集成。
从其他数据库方便地迁移工具。
支持 Flash 上的 Redis,因此我们可以将历史数据保存在较慢的存储上。
我们坚信所有具有时间序列用例的 Redis 用户都将从使用 RedisTimeSeries 中受益。
本文转载自公众号中间件小哥(ID:huawei_kevin)。
原文链接:
https://mp.weixin.qq.com/s/ZeD8ucPDNpoZr4vbaVv3PQ
评论