点 快进的未来即将来到你所在地区的活动。

加入我们 Redis 发布

t-digest:Redis Stack 中的新概率数据结构

随着 Redis Stack 的最新版本发布,我们隆重推出一个新的概率性数据结构:t-digest

如果你的输入是一系列测量值(例如温度或服务器延迟),你可以使用 t-digest 来有效地估算百分位数(例如第 50、第 90和第 99百分位数)。这仅仅是一个举例,下面我们还会解释更多用法。

对 t-digest 的支持扩展了 Redis Stack 的现有功能及其对数据模型和处理引擎的支持,其中包括 RedisInsight搜索和查询JSON时间序列 以及 概率性数据结构

在此我们将解释 t-digest 是什么、它在什么时候可以成为一个好的选择以及如何使用它。

Redis 中的概率性数据结构

Redis 已经有了大量的 概率性数据结构HyperLogLog布隆过滤器、布谷鸟过滤器、最小计数素描Top-k。你可以使用它们来回答有关数据流和大型数据集的常见 问题

  • 数据流中出现了多少个唯一的值?(HyperLogLog)
  • v 是否出现在数据流中?(布隆过滤器和布谷鸟过滤器)
  • v 在数据流中出现了多少次?(最小计数素描)
  • 数据流中最频繁的k个值是什么?(Top-k)

要获得准确的答案可能需要大量的内存。但是,如果你愿意牺牲准确性,你可以大幅减少内存需求。这些数据结构中的每一个都允许你在准确性和内存消耗之间设置一个可控的权衡。除了需要更小的内存占用空间外,概率性数据结构通常比准确的算法快得多。

正如你所见,概率性数据结构非常有趣!(至少对于软件开发人员来说是这样。)

什么是 t-digest?

t-digest 是一个概率性数据结构,可以帮助你回答以下问题

  • 数据流中哪一部分值小于给定值?
  • 数据流中有多少个值小于给定值?
  • 哪一个值小于数据流中p个百分比的值?p百分位数是多少?
  • p1个百分位数和p2个百分位数之间的平均值是多少?
  • 数据流中小 (或大) 的第 nth 的值是多少?[反向] 排名第 n 的值是多少?

Ted Dunning 在 2013 年首次引入了 t-digest,并在众多出版物上进行了论述

实际上,t-digest 可以帮助解决许多方面。以下是一些场景

硬件/软件监控 

您正在衡量在线服务器响应延迟,并想知道

  • 已衡量延迟的第 50th、90th 和 99th 个百分位数是什么?
  • 已衡量延迟中有多少百分比低于 5 个毫秒?
  • 平均延迟是多少,忽略异常值?或者,更准确地说:第 10 和 90 个百分位数之间的平均延迟是多少?

在线游戏

数百万人在您的在线平台上玩游戏。您希望为每位玩家提供以下信息

  • 您的分数优于已玩游戏环节的x 个百分比
  • y 个游戏环节左右,有人比您得分更高
  • 要优于已玩游戏的 90%,您的分数应为z

网络流量监控

您正在衡量每秒通过您的网络传输的 IP 包数量。您想快速检测潜在的拒绝服务攻击。您可能想要查询

  • 过去一秒内的包数量是否超过先前观察到的值中的 99.9%?
  • 我预计在正常网络条件下可以看到多少包,例如在第 0.5 和 99.5 个百分位数之间?

预测性维护

您正在衡量机器读数,例如噪音电平或电流消耗。要检测可疑行为,您可以查询

  • 所衡量的参数是否不规则?也就是,它不在 [第1个百分位数…第 99 个百分位数] 范围之内?或者,添加您的逻辑:过去一小时内所衡量的参数至少有 10 秒不规则?
  • 我应该将警报设为哪些值?

不用 t-digest,我无法完成所有这些吗?

当然可以。但 t-digest 让操作变得更轻松。让我们比较一下前后场景。

假设您想衡量您运行的网站上的 HTTP 请求延迟。HTTP 延迟是从用户发出请求到用户收到响应所花费的时间。 

延迟会根据许多因素而有很大差异,因此通常需要衡量延迟的第 50 个百分点、第 90 个百分点和第 99 个百分点。很明显,一半的请求服务于第 50 个百分点以下,90% 的请求服务于第 90 个百分点以下,依此类推。

在没有 t-digest 的情况下,你将如何确定这些统计数据?最简单的方法是将所有测得的延迟(每天可能达到数百万或数十亿个)存储在一个已排序的数组中。例如,要检索第 90 个百分点,你需要从已排序的数组中读取一个索引的值,该索引等于其大小的 90%。可以使用更复杂的数据结构和算法,但通常在给定一组假设(例如延迟范围和分辨率、其分布以及要查询的一组常数值)下,可以使用更复杂的数据结构和算法。

在 t-digest 中,不需要任何此类假设。此外,内存占用量很小,并且添加和查询数据非常快。

那么缺点是什么?准备容忍估计值中非常小(通常可以忽略)的相对误差。在绝大多数涉及统计信息的情况下,估计值的小误差是可以接受的。

如何使用 t-digest?

让我们来实际使用一下,看看它的工作原理。 

让我们继续 HTTP 请求延迟示例。一个选项是使用 TDIGEST.CREATE 创建一个 t-digest,并使用 TDIGEST.ADD 添加观察结果(或测量值,如果你愿意的话)。

TDIGEST.CREATE key [COMPRESSION compression] 

这将初始化一个新的 t-digest 数据结构(如果存在这样的键,则会发出错误)。COMPRESSION 参数指定了准确性和内存消耗之间的权衡。默认值为 100。值越高表示准确性越高。

要向 t-digest 中添加一个新的浮点值(观测值),请使用

TDIGEST.ADD key value [value ...]

例如,要创建一个名为 t 的摘要,其压缩设置是 1000(非常准确),并添加 15 个观测值,我们将输入

TDIGEST.CREATE t COMPRESSION 1000
TDIGEST.ADD t 1 2 2 3 3 3 4 4 4 4 5 5 5 5 5

每当有新观测值时,你可以重复调用 TDIGEST.ADD。要按分数或等级估算值,请使用 TDIGEST.QUANTILE

TDIGEST.QUANTILE key quantile [quantile …] 

对于每个输入分数,它返回对(浮点数)的估计,该值小于给定分数的观测值。换句话说,分数 0.9 等效于第 90 个百分点。

以下查询可检索小于观察到的延迟的 0%、10%、20% … 100% 的延迟

TDIGEST.QUANTILE t 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
1) "1"
2) "2"
3) "3"
4) "3"
5) "4"
6) "4"
7) "4"
8) "5"
9) "5"
10) "5"
11) "5"

该查询包含 11 个分数;因此,回复是包含 11 个延迟值的数组。

分数为 0 (0%) 时,结果是最小的观察值,在本例中为 1。类似地,当分数为 1 (100%) 时,结果是最大的观察值(或示例中的 5)。对于这两个分数,其结果总是准确的。对于任何其他分数,其结果都是估计值。

您可以看到 10% 的延迟小于或等于 2;20% 和 30% 的延迟小于或等于 3;40%、50% 和 60% 的延迟小于或等于 4;最后,70%、80%、90% 和 100% 的延迟小于或等于 5。

您还可以查询n 个最小已测量延迟。为此,请使用 TDIGEST.BYRANK

TDIGEST.BYRANK key rank [rank …]

它会按给定的rank 为每个输入返回一个带 rank 的value 的估算值(浮点)。

以下查询会检索第一个、第二个、… 和第九个最小的观察延迟

TDIGEST.BYRANK t 0 1 2 3 4 5 6 7 8 9
1) "1"
2) "2"
3) "2"
4) "3"
5) "3"
6) "3"
7) "4"
8) "4"
9) "4"
10) "4"

查询包含 10 个 rank;因此,回复是一个包含 10 个值的数组。

当 rank 为 0 时,结果是最小的观察值(在本例中为 1)。类似地,当 rank 等于观察值数减一,结果是最大的观察值。对于这两个 rank,其结果总是准确的;对于任何其他 rank,其结果都是估计值。当 rank 大于或等于观察值数时,结果为 inf。

您可以看到第二个和第三个最小的延迟(具有 rank 1 和 rank 2 的观察值)为 2。类似地,第三个、第四个和第五个最小的延迟(具有 rank 3、4 和 5 的观察值)为 3,第六个、第七个、第八个和第九个最小的延迟(具有 rank 6、7、8 和 9 的观察值)为 4。

当然,您可以查询n 个最大的已测量延迟。请使用 TDIGEST.BYREVRANK 进行查询。

TDIGEST.BYREVRANK key reverse_rank [reverse_rank …]

它会按给定的reverse rank 为每个输入返回一个带该 reverse rank 的value 的估算值(浮点)。

以下查询会检索第一个、第二个、… 和第九个最大的观察延迟

TDIGEST.BYREVRANK t 0 1 2 3 4 5 6 7 8 9
1) "5"
2) "5"
3) "5"
4) "5"
5) "5"
6) "4"
7) "4"
8) "4"
9) "4"
10) "3"

查询包含 10 个 reverse rank;因此,回复是一个包含 10 个值的数组。

当 reverse rank 为 0 时,结果是最小的观察值(在本例中为 5)。类似地,当 reverse rank 等于观察值数减一,结果是最小的观察值。对于这两个 reverse rank,其结果总是准确的。对于任何其他 reverse rank,其结果都是估计值。当 rank 大于或等于观察值数时,结果为 -inf。

你可以看到第一、二、三、四和第五大延迟(分别具有反向排名为 0、1、2、3 和 4 的观察)分别是 5。同样,第六、七、八和第九大延迟(分别具有反向排名为 5、6、7 和 8 的观察)是 4。

要根据值评估分数,请使用 TDIGEST.CDF: 

TDIGEST.CDF 键值 [值 …]

它为每个输入检索观察值少于给定值的一组评估,以及等于给定值的一组观察值的一半。

以下查询检索 小于 0、1、2、3、4、5 和 6 毫秒的延迟的分数

TDIGEST.CDF t 0 1 2 3 4 5 6
1) "0"
2) "0.033333333333333333"
3) "0.13333333333333333"
4) "0.29999999999999999"
5) "0.53333333333333333"
6) "0.83333333333333337"
7) "1"

该查询包含七个延迟值;因此,应答是一组七个分数。

正如你所看到的,此简单示例中的所有评估都很准确:小于 0 的延迟分数加上等于 0 的延迟的一半为 0。同样,小于 1 的延迟分数加上等于 1 的延迟的一半为 3.33%,依此类推。

有时,您想要评估观察数量,而不是观察分数。为此,您可以使用 TDIGEST.RANK 和 TDIGEST.REVRANK。 

以下查询分别检索小于 0、1、2、3、4、5 和 6 毫秒的延迟的数量

TDIGEST.RANK 键值 [值 …]

这类似于 TDIGEST.CDF,但对每个输入,它返回一个小于给定值加上等于给定值的一半的观察的数量评估。

TDIGEST.RANK t 0 1 2 3 4 5 6
1) "-1"
2) "1"
3) "2"
4) "5"
5) "8"
6) "13"
7) "15"

该查询包含七个延迟值;因此,应答是一组七个排名。

同样,此示例中的所有评估都很准确:没有小于 0 的延迟;因此,结果排名为 -1。小于 1 的延迟数量加上等于 1 的延迟一半为 1。同样,小于 2 的延迟数量加上等于 1 的延迟一半为 2,依此类推。

TDIGEST.REVRANK 键值 [值 …]

这类似于 TDIGEST.RANK,但对每个输入,它返回一个大于给定值和等于给定值的一半的观察的数量评估()。

以下查询检索分别大于 0、1、2、3、4、5 和 6 毫秒的延迟数

TDIGEST.REVRANK t 0 1 2 3 4 5 6
1) "15"
2) "14"
3) "13"
4) "10"
5) "7"
6) "2"
7) "-1"

该查询包含七个延迟值;因此,应答是一组七个反向排名。

再一次,您可以看到本示例中的所有估计都是准确的:大于 0 的延迟数加上等于 0 的延迟数的一半为 15。同样,大于 1 的延迟数加上等于 1 的延迟数的一半为 14。没有延迟数等于或大于 6;因此,得出的逆序数为 -1。

我们可以看到,对于最小观察值与最大观察值之间的任何 v,TDIGEST.RANK(v) + TDIGEST.REVRANK(v) 等于观察值的数量。

计算平均测量值是一项常见操作。然而,有时测量是有偏差的或包含无效值。例如,考虑一个有偏差的无效延迟值:999999999 毫秒。当出现这种情况时,一项常见做法是计算忽略异常值的所有观察值的平均值。例如,您可能需要计算第 20 百分位数与第 80 百分位数之间的平均值。

要估计指定分数之间的平均值,请使用 TDIGEST.TRIMMED_MEAN

TDIGEST.TRIMMED_MEAN 键 lowFraction highFraction

TDIGEST.TRIMMED_MEAN t 0.2 0.8
"3.8888888888888888"
TDIGEST.TRIMMED_MEAN t 0.1 0.9
"3.7692307692307692"
TDIGEST.TRIMMED_MEAN t 0 1
"3.6666666666666665"

有时,合并 t-digest 数据结构很有用。例如,假设我们测量三台服务器的延迟,每台服务器都有自己独特的 t-digest,但我们想要计算出合并所有服务器的延迟的 90%、95% 和 99%。

使用此命令将多个 t-digest 数据结构合并成一个

TDIGEST.MERGE 目标键 numKeys 源键… [COMPRESSION 压缩] [OVERRIDE]

TDIGEST.CREATE s1
TDIGEST.ADD s1 1 2 3 4 5
TDIGEST.CREATE s2
TDIGEST.ADD s2 6 7 8 9 10
TDIGEST.MERGE sM 2 s1 s2
TDIGEST.BYRANK sM 0 1 2 3 4 5 6 7 8 9
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
10) "10"

 

使用 TDIGEST.MINTDIGEST.MAX 分别检索 t-digest 数据结构中的最小值和最大值。

当数据结构为空时,两者都会返回 nan

TDIGEST.MIN t
"1"
TDIGEST.MAX t
"5"

这两个命令都会返回准确的结果,分别等同于 TDIGEST.BYRANK key 0TDIGEST.BYREVRANK key 0

使用 TDIGEST.INFO 检索有关 t-digest 的附加信息,包括添加到数据结构的观察值的数量和为数据结构分配的字节数量。

要清空 t-digest 数据结构并重新初始化它

TDIGEST.RESET 键

下一步

t-digest 扩展了 Redis 不断增长的概率数据结构集,其可帮助您解决更多与流式数据和大数据集相关的用例。它以亚毫秒延迟、亚线性内存需求和极高的准确性来实现此功能。

这篇博文是一般概述。所有 t-digest 命令均在 redis.io 中进行了解释。

从我们的下载中心下载最新版本或通过FlatHubSnapcraf安装。