Redis 命令及数据类型 -- TDigest

摘要

T-Digest(分位数估计算法)

  • T-Digest(TDigest)解决的问题与 CMS、TopK 完全不同,核心目标是 分位数(quantile)统计。

  • T-Digest 由 Ted Dunning 提出,专门为 高精度尾部分位数 设计。

  • TDigest 可以在极低内存占用下,近似计算分位数(P50 / P90 / P99 / Median 等)。

  • 分位数(Percentile)指标对照表

指标 英文全称 数学含义 通俗解释 典型业务解读 常见使用场景
Median Median 排序后位于中间位置的值 一半数据在它左右 “一般用户的体验” 基础体验评估
P50 50th Percentile 50% 的数据 ≤ 该值 和 Median 完全相同 “典型请求耗时” 常规性能监控
P90 90th Percentile 90% 的数据 ≤ 该值 10% 的请求更慢 “大多数用户的体验” 业务体验监控
P95 95th Percentile 95% 的数据 ≤ 该值 5% 的请求更慢 “尾部开始恶化” SLA 边界监控
P99 99th Percentile 99% 的数据 ≤ 该值 1% 的请求最慢 “极端但真实的用户体验” 核心 SLA / SLO
P99.9 99.9th Percentile 99.9% 的数据 ≤ 该值 千分之一最慢请求 “极端尾延迟” 金融 / 核心链路
Max Maximum 数据中的最大值 最慢的那一次 噪音极大 几乎不用

一个快速理解示例

1
2
3
4
5
6
7
8
9
10
11
假设 100 次请求:
99 次耗时:10 ms
1 次耗时:3000 ms

所以:
P99 = 3000 ms
P90 = 10 ms
P50/Median = 10 ms
平均值 = (99*10 + 1*3000)/ 100 = 39.9 ms

👉 只有 P99 真实暴露了问题。
  • 工程实践中的“标准搭配”

应用场景 常看指标
Web / API 服务 P90 / P99
微服务链路 P99 / P99.9
数据库 / 存储 P95 / P99
前端体验 P50 / P90
  • 一句话:TDigest 用于统计数字的分布,P50 看中间,P90 看大多数,P99 看最差那 1%

RedisBloom 中 TDigest 的核心命令

创建 TDigest

1
2
3
4
5
6
7
8
9
10
11
12
13
# TDIGEST.CREATE key [COMPRESSION compression]
# 参数说明
# key: TDigest 的名称
# compression: 压缩级别,默认为 100,越大 → 精度越高 → 内存越大
# 示例
TDIGEST.CREATE latency:td COMPRESSION 200
# 返回值
OK

# 获取 TDigest 的类型
type latency:td
# 返回值
TDIS-TYPE

添加样本

1
2
3
4
5
# TDIGEST.ADD key value [value ...]
# 示例
TDIGEST.ADD latency:td 12.3 15.7 100.4
# 返回值
OK

查看最大/最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
# 获取最大值
# TDIGEST.MAX key
# 示例
TDIGEST.MAX latency:td
# 返回值
"100.4"

# 取最小值
# TDIGEST.MIN key
# 示例
TDIGEST.MIN latency:td
# 返回值
"12.3"

查询分位数

1
2
3
4
5
6
7
8
9
10
# TDIGEST.QUANTILE key quantile [quantile ...]
# 参数说明
# key: TDigest 的名称
# quantile: 查询的分位数,0.5=>P50 / 0.9=>P90 / 0.99=>P99
# 示例
TDIGEST.QUANTILE latency:td 0.5 0.9 0.99
# 返回值
1) "15.7"
2) "100.4"
3) "100.4"

反向查询(值 → 百分位)

1
2
3
4
5
6
7
8
# TDIGEST.CDF key value [value ...]
# 示例
TDIGEST.CDF latency:td 12.3 15.7 100.4 20
# 返回值
1) "0.16666666666666666"
2) "0.5"
3) "0.8333333333333334"
4) "0.6666666666666666" # 这里注意 value 不必须在 TDIGEST 中,这里的含义是 P66=20,即 66% 的值都 ≤ 20

合并 TDigest

1
2
3
4
5
6
7
8
9
10
11
# TDIGEST.MERGE destination-key numkeys source-key [source-key ...] [COMPRESSION compression] [OVERRIDE]
# 参数说明
# destination-key: 目标 TDigest 的名称
# numkeys: 源 TDigest 的数量
# source-key: 源 TDigest 的名称
# compression: 压缩级别,默认为 100,越大 → 精度越高 → 内存越大
# OVERRIDE: 是否覆盖目标 TDigest,默认为 false
# 示例
TDIGEST.MERGE latency:td 2 latency:td:1 latency:td:2 COMPRESSION 200 OVERRIDE
# 返回值
OK

获取近似排名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 正向排名
# TDIGEST.RANK key value [value ...]
# 示例
# TDIGEST.ADD latency:td 12.3 15.7 100.4 # 假设这些数据已经写入了 TDigest
# 如果把 value 放入当前分布中,它的 rank(小于该值的样本数量)是多少
TDIGEST.RANK latency:td 0 12.3 15.7 20 100.4 200
# 返回值
1) (integer) -1 # 0: 小于最小值 12.3 → 返回 -1
2) (integer) 0 # 12.3: 第 0 位 → 返回 0,真实存在的位数
3) (integer) 1 # 15.7: 排在 12.3 后 → 返回 1,真实存在的位数
4) (integer) 2 # 20: 介于 15.7 与 100.4 之间 → 返回 2
5) (integer) 2 # 100.4: 第 2 位 → 20,真实存在的位数
6) (integer) 3 # 200: 大于 100.4 → 返回 3

# 反向排名
# TDIGEST.REVRANK key value [value ...]
# 示例
TDIGEST.REVRANK latency:td 0 12.3 15.7 20 100.4 200

根据排名获取元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 正向排名
# TDIGEST.BYRANK key rank [rank ...]
# 示例
TDIGEST.BYRANK latency:td 0 1 2 3
# 输出结果
1) "12.3"
2) "15.7"
3) "100.4"
4) "inf" # 不存在

# 反向排名
# TDIGEST.BYREVRANK key rank [rank ...]
# 示例
TDIGEST.BYREVRANK latency:td 0 1 2 3

计算 TDigest 内部样本 去掉尾部极值后的平均近似值(Trimmed Mean,截断均值)

  • 去掉数据分布的极端尾部,只算主要集中区域的平均值,这样更能代表“绝大多数用户的体验”

1
2
3
4
5
6
7
8
9
# TDIGEST.TRIMMED_MEAN key low_cut_quantile high_cut_quantile
# 参数说明
# key: TDigest 的名称
# low_cut_quantile: 去掉的左侧百分比
# high_cut_quantile: 去掉的右侧百分比
# 示例
TDIGEST.TRIMMED_MEAN latency:td 0.05 0.95
# 输出结果
"42.800000000000004"

获取 TDigest 的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# TDIGEST.INFO key
# 示例
TDIGEST.INFO latency:td
# 输出结果
1) Compression # 压缩级别,控制质心的数量与精度。值越大 → 精度越高,内存稍大。
2) (integer) 200
3) Capacity # 质心最大容量。表示内部能容纳的最大节点数,实际使用中 TDigest 会动态合并质心以控制内存。
4) (integer) 1210
5) Merged nodes # 已合并的质心数量。这些是 TDigest 当前压缩后的节点,用于计算 rank / quantile。
6) (integer) 3
7) Unmerged nodes # 未合并的节点数量。表示新加入但尚未压缩到质心的样本。
8) (integer) 0
9) Merged weight # 已合并质心的总权重。每个样本权重默认为 1,3 表示目前总共有 3 个样本被合并进质心。
10) (integer) 3
11) Unmerged weight # 未合并节点的权重总和。0 表示没有未压缩的样本
12) (integer) 0
13) Observations # 观测到的样本总数。你 TDIGEST.ADD 的 3 个样本正好对应这个值。
14) (integer) 3
15) Total compressions # 已经执行的压缩次数。每次 TDigest 内部质心合并称为一次压缩。
16) (integer) 1
17) Memory usage # Redis 为这个 TDigest 分配的内存(字节)。包含质心、索引、结构开销。TDigest 内存使用是固定的、与样本数量无关(主要由 Compression 决定)
18) (integer) 19368

清空元素

1
2
3
4
5
# TDIGEST.RESET key
# 示例
TDIGEST.RESET latency:td
# 输出结果
OK

TDigest 的内存与精度

维度 特性
内存占用 与 COMPRESSION 成正比(通常 KB 级)
精度 尾部分位数(P95/P99)极高
写入复杂度 近似 O(log n)
查询复杂度 O(log n)

与样本数量无关。