导读:性能分析等场景对实时分位数有强烈诉求。在计算累计时长时,能够将不一样时间段的时长简单相加,而分位数却没法先计算不一样维度下的分位值,而后对其直接聚合,该特性对实时计算带来了较大挑战。咱们基于TDigest数据结构,利用Redis和Doris等高性能存储,预先计算全部可能查询的分位值指标,既能够快速计算指标,同时能够保障查询效率。该系统已经对百度内内核性能、网络性能等业务场景进行输出,并能有效知足业务高时效的分析需求。前端
全文3663字,预计阅读时间10分钟。算法
1、问题描述与技术挑战
在实际工做中,咱们发现许多业务场景中都有对某一数值型指标实时统计分位数的需求,通常要求计算结果有很高准确率同时具有极低的计算延迟,实现这类需求给数据RD的开发工做带来必定的挑战,其中主要的技术挑战包括如下三个方面:后端
没法对全量数据进行排序:因为在实时计算场景中是逐条处理数据的,没法对全量数据排序,进而没法得到全量数据的分位数数组
计算逻辑复杂,计算延迟高:即时在可以排序的场景中,高复杂度的排序操做也会带来很高的计算延迟,没法知足实时计算的低延迟要求性能优化
分位数结果没法聚合:两个计算得出的分位数结果没法像求和结果那样直接累加合并获得新的结果,这为分位数计算结果的存储方式带来挑战网络
针对上述问题,咱们基于TDigest数据结构,实现了实时计算环境下的分位数计算方法,封装为基础组件并向上提供API接口,能够在不一样的业务场景(内核性能、搜索性能、PUSH等)下提供实时、准确的分位数计算。数据结构
2、基础架构与解决方案
本节咱们将从计算分位数的经常使用数据结构、咱们实现分位数计算的基础架构、解决方案三部分介绍流式计算场景下的分位数计算方法:架构
2.1 分位数的经常使用数据结构
TDigest计算分位数app
TDigest是一个简单,快速,精确度高,可并行化的近似百分位算法,被Spark, ES, Kylin等系统使用。TDigest的核心思想是经过聚类的方法将离散的数据点汇集为多个不一样的质心,在经过线性插值法计算分位数,线性插值法是最简单的插值算法。性能
通俗的讲:传统方法是对离散的数据进行排序,在排序结果中直接得到分位数。而TDigest是将离散的数据聚类为多个质心,而后对质心进行近似的“排序”,最后经过插值法求取分位数。
如上图所示,将离散的数据点(图中无色的数据点)聚类为多个不一样的质心(图中彩色的数据点),其中每一个质心周围的数据点数决定了该质心所占的权重(图中质心的大小),最后经过对全部的质心进行排序,就可使用线性插值法求取对应的分位数,其中数据点与质心的距离和权重关系以下图所示。
特别地,在每一个TDigest建立时有一个重要的compression参数,主要用于在计算的精确度与空间复杂度之间作权衡:
当compression参数设置越大时,聚类获得的质心越多,则差分法求取的分位数精确度越高
当compression参数设置越大时,TDigest数据结构占用的存储空间越大,则分位数计算的空间复杂度越高
设置合适的compression参数,可以在提升计算准确率的同时,尽量下降存储空间,从而知足业务的实际需求
为了帮助你们在作分位数计算时可以选取合适的参数,咱们选择百万级的数据量(即统计100w个随机变量的分位数),在不一样参数下的计算精确度和空间复杂的以下表所示:
针对上表所示的数据,咱们将作出如下三点说明:
本次测试使用MergingDigest数据结构,该结构占用的空间与compression参数的取值有关,与统计的数据量无关;
随着数据量的增大,compression的取值应适当增大,可以有效提升计算的准确率
2.2 分位数组建的基础架构
因为实时分位数计算是一个常见统计方法,在许多业务场景都会提出相似的需求,对需求方关注的统计指标计算不一样的分位数。
为节约人力成本,缩短迭代开发的时间周期,咱们基于TDigest数据结构,封装了通用的基础组件,从而在不一样的业务场景下快速实现实时分位数统计的开发。
如上图所示,在实时分位数计算的通用组件中,其基础架构和执行过程主要分为如下几个关键步骤:
1) 从上游业务方读取须要统计分位数的原始数据
-
根据业务方需求的分组规则,按分组聚合为TDigest数据结构,将聚合结果存入Redis中,或与Redis中已存在对应的数据进行合并,以获取准确的计算结果
-
从TDigest结构中获取分位数的计算结果,并向上返回
综上所述,咱们经过封装基础组件并向上提供API的方式,实现了通用、灵活且对应用方透明的分位数计算方法,可以保证明时性的同时,实现高准确率、低空间复杂度的分位数计算,目前已经在性能平台、搜索、PUSH等厂内多个业务需求中落地应用
2.3 总体实现方案
基于上述介绍的实时分位数基础组件,在厂内的大多数业务场景中,一般从消息队列中获取应用方上报的原始数据,通过一系列解析和计算后,将计算结果存储Doris等OLAP引擎或DB中,共需求方查询和生成对应报表,这是一个通用的解决方案。
根据上述分析,咱们就能够获得一个分位数实时计算做业的基本架构,其架构模型以下图所示:
如上图所示,在厂内的环境中,实时分位数计算任务的经常使用基本架构主要包括如下几个关键步骤:
1)从消息队列中读取业务方上报的基础数据,并按业务逻辑进行数据解析;
2)经过FlatMap方法,按不一样字段将一条数据展开为多条(具体内容将在第3节详细介绍);
3)根据业务设计的查询维度,按不一样的key对数据进行分组操做
4)分别将每一个key的数据合并为一个TDigest数据结构
5)将聚合后的数据与Redis中存储的数据进行合并,同时将合并结果写回Redis中
6)最后根据数据聚合结构,从每一个分组对应的TDigest结构中获取对应的分位数
综过以上步骤,能够实现高实时性、高精确度和低空间复杂度的实时分位数计算方法,可以知足大多数实时分位数业务的需求,在更多的业务场景中可能须要根据实际需求进行适当的调整。
3、解决分位数没法聚合的问题
3.1 问题描述
在实际的业务需求中,咱们可能须要按照不一样的时间、查询维度等信息检索统计的分位数。可是,已经计算好的两个分位数结果是没法进行聚合操做的。
例如:针对手百APP的用户访问时长,咱们能够将某一天中每一个小时访问时长的和(SUM)进行累加,从而得到这一天的访问时长总和。但咱们若是记录了每一个小时中访问时长的80分位数,则没法对这些分位数进行聚合,即没法求得这一天中访问时长的80分位数。这种现象被称为分位数的“不可聚合性”
所以,在实际应用中,若是业务需求要对不一样时间、不一样维度下的指标分位数进行任意聚合、查询等操做,就为分位数的计算和存储提出新的技术挑战。
3.2 分位数聚合方案
针对上述问题,咱们提出按全部查询维度进行提早聚合计算的解决方案,即针对每一种可能出现的查询维度组合,咱们都提早计算分位数并存储,这样在查询过程当中直接检索对应查询维度的聚合计算结果,在解决了分位数的“不可聚合性”问题的同时,也避免了重复的聚合计算带来的时间开销,缩短了查询耗时,提高了用户体验。
接下来,咱们经过一个简单实例讲解具体的聚合计算方法:假设在某业务场景中,用户关注的查询维度共有三个字段,分别为:APP版本(app_version)、厂商(manufacturer)和客户端操做系统版本(os_version)。则对于任意维度组合的查询操做,用户有可能采用 2^3=8 种聚合查询方式。所以,咱们经过排列组合的方式,枚举中全部可能的聚合查询方式,分别统计分位数。假设从上游读取到的部分数据以下表所示:
而且,若是对某一字段进行聚合查询,咱们将该字段的取值记为关键词 “ALL”,则这条数据共对应2^3=8 种可能的聚合查询方式。为了模拟出8种不一样的维度排列组合方式,咱们利用二进制排列组合的方式,让每一个字段严格对应二进制数据中的一位:若是该位的取值为0,则字段内容为上报的原始值(即上表中的实际取值);若该位的取值为1,则对应字段的取值记为关键词“ALL”。此外,二进制数据中从右至左每一位与字段的对应关系为:
第1位对应os_version
第2位对应manufacturer
第3位对应app_version
由此可得,任意字段聚合查询的排列如何方式以下表所示:
这样,咱们就经过二进制排列组合的方式,枚举出全部可能的维度组合查询方式。在实际的计算过程当中,能够利用流式计算的FlatMap算子,按照上述的排列组合方式,将一条数据扩展为多条数据,并进行分组聚合、计算分位数,将最终的计算结果存入Doris等存储引擎中供用户查询。此时,计算结果中实际已经包含了全部可能的聚合查询方式,业务方能够按须要直接查询到最终的分位数结果,而无需另外进行聚合计算操做,在有效提升查询效率的同时保证了用户体验。
4、结语
以上内容是咱们从宏观的角度,对实时分位数计算方法的核心技术、基础架构和技术难点进行了简要介绍。若有任何问题或建议,欢迎你们随时沟通交流。
本期做者 | 子阳,负责百度性能平台的实时数据开发工做,主要研究方向为流式计算、智能预测等
招聘信息
百度APP技术平台研发部负责百度APP和百家号技术平台建设,也承载着PUSH、消息、互动、交易、日志、性能、审核、B端等一系列标杆中台的建设,欢迎你们加盟,期待着你的到来!
不管你是后端,前端,仍是大数据,这里有若干职位在等你,欢迎投递简历,【联系方式同名公众号百度Geek说,输入内推便可】,百度APP技术平台研发部期待你的加入!
阅读原文
推荐阅读
---------- END ----------
百度Geek说
百度官方技术公众号上线啦!
技术干货 · 行业资讯 · 线上沙龙 · 行业大会
招聘信息 · 内推信息 · 技术书籍 · 百度周边
欢迎各位同窗关注