起因
一般的聚合分析中较为常见的 percentiles 百分位数分析:n 个数据按数值大小排列,处于 p% 位置的值称第 p 百分位数。
当数据量较小或者数据集中存储在同一位置时,用上述类似的百分位数分析方法就很容易。
但当数据量不断增长时,对于数据进行聚合分析就需要在数据量,精确度和实时性三个方面进行取舍,只能满足其中两项。
T-Digest算法
TDigest就是一种简单,快速,精确度高,可并行化的近似百分位算法,被Spark,ES,Kylin等系统使用。TDigest主要有两种实现算法,一种是buffer-and-merge算法,一种是聚类算法。
TDigest 使用的思想是近似算法常用的 Sketch,也就是素描,用一部分数据来刻画整体数据集的特征,就像我们日常的素描画一样,虽然和实物有差距,但是却看着和实物很像,能够展现实物的特征。
我们知道,概率密度函数(PDF)上的某一点的 y 值就是其 x 值在整体数据集中的出现概率,整个函数的面积相加就正好为 1 ,可以说它刻画了数据在数据集中的分布态势。(如下示意的正态分布)
PDF 函数曲线中的点都对应着数据集中的数据,当数据量较少时,我们可以使用数据集的所有点来计算该函数,但是当数据量较大时,我们只有通过少量数据来代替数据集的所有数据。
此时,我们需要将数据集进行分组,相邻的数据分为一组,用 平均数(Mean)和 个数(Weight)来代替这一组数。这两个数合称为 Centroid(质心数),然后用这个质心数来计算 PDF,这就是 TDigest 算法的核心思想。
通常,质心数的值越大,表达它代表的数据越多,丢失的信息越大,也就越不精准。如上图所示,太大的质心数丢失精准度太多,太小的质心数则有消耗内存等资源较大,达不到近似算法实时性高的效果。
所以,TDigest 在压缩比率(压缩比率越大,质心数代表的数据就要越多)的基础上,按照百分位数来控制各个质心数代表的数据的多少,在两侧的质心数较小,精准度更高,而在中间的质心数则较大,以此达到前文所说的 1% 或 99% 的百分位要比 50% 的百分位要准确的效果。
算法具体实现
ElasticSearch 直接使用了 TDigest 的开源实现 T-digest,其 github 地址为https://github.com/tdunning/t-digest。T-digest 提供了两种 TDigest 算法的实现:
MergingDigest 对应上文所说的 buffer-and-merge 算法
AVLGroupTree 对应 AVL 树的聚类算法。
MergingDigest 用于数据集已经排序的场景,可以直接根据压缩比率计算质心数,而 AVLGroupTree 则需要使用 AVL 树来自信对数据根据其 " 接近程度 " 进行判断,然后计算质心数。
MergingDigest 的实现较为简单,顾名思义,其算法名称叫做 buffer-and-merge,所以实现上使用 tempWeight 和 tempMean 两个数组来代表质心数数组,将数据和保存的质心数进行 merge,然后如果超出 weight 上限,则创建新的质心数,否则修改当前质心数的平均值和个数。
而 AVLGroupTree 与 MergingDigest 相比,多了一步通过 AVL 二叉平衡树搜索数据最靠近质心数的步骤,找到最靠近的质心数后,也是将二者进行 merge,然后判断是否超过 weight 上线,进行修改或者创建操作。
参考:ElasticSearch 如何使用 TDigest 算法计算亿级数据的百分位数?