一、背景与问题
SaaS服务不管单服务还是链路压测基本可以用下图的过程概括:
本文主要介绍在SaaS服务常态化压测流程中「风险评估」环节的方法和实践,即如何对服务流量进行风险分析。
什么是「风险评估」?
常态化压测的主要目的是评估服务容量是否能够承载业务高峰期流量,而流量是处于不断变化的,这就导致压测结论具有时效性,从而迫使业务方需要定期观测并评估流量风险。而评估成本视服务复杂度和接口规模而定,主要痛点体现为:
- 分析成本高(平均大于1人时/每服务);
- 分析能力参差不齐;
- 风险分析和压测模型生成方面缺少有效方法。
为了应对常态化压测在效率上的挑战,我们将「风险评估」需要解决的问题总结为两个:
- 如何评估被测服务最近是否存在流量风险?
一方面,对于流量快速增长的服务来说,如果我们能快速感知流量变化,更利于我们及时做出应对,减少风险概率。另一方面,按照经验来讲,任何服务的用户规模都不是一直增长的,用户规模稳定后服务的访问量也会进入平稳期,在不考虑代码变更影响性能的情况下,高频的容量压测存在非必要投入。如果容量压测的主要目标是应对性能与流量变化,那在压测前如果能够对流量变化定量分析以判断是否存在风险,就显得很有必要。 - 如何设计更有效的压测模型?
用于常态化压测的模型主要达到一个目的:验证服务总流量和各接口可以支撑N倍线上峰值。但总流量和接口目标往往很难兼顾。
设计压测接口QPS的通常做法是取历史峰值QPS的倍数(倍数通常为2或3),然后压测总流量就是各个接口峰值的叠加。由于接口QPS峰值往往不是出现在同一时间(如图1.1),简单叠加会导致压测总流量远超线上总流量的2或3倍(如图1.2)。其代价就是需要更多的服务器。通常我们很难做到兼顾接口QPS达标(有效性)与压测总流量更拟合线上总流量(成本)两个方面。图1.1 不同接口24小时QPM数据
图1.2 通过接口QPM峰值聚合出来的压测总流量与线上总流量峰值
二、概述
本文旨在分享解决上述两个问题的一种思路与方法,下面会对其进行详细描述,总体来讲,流量风险分析可用下述过程来表示:图2.1 分析处理过程示意
- 通过「分析流量风险」计算出服务的流量风险系数和相关数据(高峰期时窗、峰值变化等),供用户深入分析和决策
- 基于峰值数据计算压测模型,供压测参考
后面的内容主要分为3个部分:
- 问题分析:将「流量风险分析」和「压测模型生成」的问题进一步定义清楚,并给出解决思路。
- 解决方案:将解决思路还原为具体的分析流程,并阐述一些实现上的取舍。
- 总结:应用效果、现存问题。
三、问题分析
这部分内容会着重在两个方面:1、流量风险的定义和量化思路;2、生成优化的压测模型。
3.1 如何定义流量风险
这里我们不对“风险”做词源考据,因为在我们的语境下共识更重要。虽然大家对“流量风险”的理解存在差异,但通常来讲,当线上流量超出或将要超出预期时,会增大故障或不可预计事件发生的概率,尤其当服务没有配置或没有完全配置限流的情况下。所以,我们把流量风险定义为“超出预期”,这里包含两个层面的“超出预期”:服务总流量、服务单个接口流量。即不仅考虑总流量,还要考虑单接口流量变化带来的影响,主要是基于以下事实:
- 服务接口较多时,只关注总流量变化容易忽略单接口的变化,从而错过风险控制的契机。出于压测成本的考虑,实际压测总会忽略掉一些不重要的接口,这些接口可能一开始流量并不满足压测要求,但时间长了流量大了需要重新关注到这些接口。比如,一些小流量接口在用户场景、上游调用关系发生变化时产生流量陡增。
- 实践中,经常会因为上游代码逻辑问题出现下游调用放大的情况,这也迫使我们需要关注接口层面。
- 由用户行为导致的自然流量高峰期不会频繁发生时窗改变,而一些非自然流量(跑批、内部运营等)确实经常会出现时窗和峰值的剧烈变动,通常情况下这些流量都是治理目标。
综上所述,风险分析需要立足于接口层面,先判断接口的风险,再进一步判断总流量的风险。
3.2 如何量化风险
3.2.1 计算接口风险
从风险定义出发,可以看出量化风险的关键其实就是量化预期,为了方便阐述,这里先假设存在这样一个服务,它在某一时刻的接口流量峰值如下:
接口 | 线上峰值QPM | 峰值时间 | 预期 |
---|---|---|---|
m1 | 100 | 11:00 | ? |
m2 | 200 | 11:10 | ? |
m3 | 300 | 11:20 | ? |
m4 | 120 | 13:20 | ? |
m5 | 280 | 13:30 | ? |
m6 | 89 | 18:20 | ? |
表3.1 各接口峰值QPM
现在真正的问题是:如何根据能够获取的数据描述一个合理的预期?
对于预期最直观的一种看法是:线上容量<压测容量的n%,0 < n < 100。但仔细一想存在问题:n与n+1是否有本质上的差别?
根据实践和可计算性,我们把“预期”定义为:单机房可能承受的线上最大QPM (记为QPMp)< 单机房已验证的最大容量(记为QPMv)。要计算出接口的已验证最大容量,需要考虑服务的机房分布,这里先给出计算公式:
线上峰值QPM / 压测峰值QPM < (M-1)/M
*M=机房数量
对于公式的解释如下:
单机房可能承受的线上最大QPM (记为QPMp)< 单机房已验证的最大容量(记为QPMv)
- “单机房可能承受的线上最大QPM”是指:考虑单机房故障的情况下,线上流量会由M-1个机房承载,故QPMp = 线上峰值QPM / (M - 1)。
- "单机房已验证的最大容量"可由描述直接得出:QPMv = 压测峰值QPM / M
由定义可得出:线上峰值QPM / 压测峰值QPM < (M-1)/M。
举例:2机房QPM(线上峰值) < 0.5 QPM(压测峰值) , 3机房QPM(线上峰值) < 0.67 QPM(压测峰值)。
\( \color{red} !!!上述公式只对应于多机房机器数量分布和负载均匀的情况!!! \)
3.2.2 计算整体风险
现在我们知道了接口风险的定义和计算方式,但对服务流量风险的量化还没结束,以表3.1为例,我们计算得到各个接口的风险预期如下表(3.2):
接口 | 线上峰值QPM | 峰值时间 | 压测峰值QPM | 线上峰值QPM / 压测峰值QPM | 预期(< 0.5) | 风险系数θ |
---|---|---|---|---|---|---|
m1 | 100 | 11:00 | 150 | 0.67 | 不满足 | 1 |
m2 | 200 | 11:10 | 280 | 0.71 | 不满足 | 1 |
m3 | 300 | 11:20 | 700 | 0.43 | 满足 | 0 |
m4 | 120 | 13:20 | 300 | 0.4 | 满足 | 0 |
m5 | 280 | 13:30 | 360 | 0.78 | 不满足 | 1 |
m6 | 89 | 18:20 | 200 | 0.45 | 满足 | 0 |
表3.2 服务各接口风险计算结果
由于表3.2是简化后的数据,实际应用中服务的接口可能有很多,而且并不一定所有接口都有压测峰值数据,用户除了关心单接口的风险以外,更需要关心服务总流量的风险水位,我们需要一种能够量化服务整体风险的方法。
由于总流量也可用上述公式描述是否符合预期,这样可以计算出各个接口和总流量的风险系数θ,由于各个接口流量存在大小上的差异,对服务整体的影响也不一样,根据接口风险加权是满足用户诉求的一种比较直观的计算方式。由此我们将整体风险系数定义为F(θ),其计算方法如下:
3.3 如何生成压测模型
在采集到服务接口流量,完成接口风险分析后,接下来需要根据流量数据构造出压测模型以便用户实施压测,从而刷新风险预期。
本文开头的问题已经指出,我们构造压测模型的通常做法是叠加各个接口的峰值QPS,按线性方式逐步放大压测流量直至达到目标,这样做会导致总流量放大,尤其一些接口调用量分布很离散的服务,比如图3.1:图3.1 不同接口24小时QPM数据
可想而知如果按接口2倍峰值压测,压测总流量会比线上总流量峰值会远高于2倍。当前的方法不能说不是压测模型,而更像是完整模型的一个片段,它只反映了线上流量某个时刻的情况。
解决这个问题的思路也比较直接:恢复压测流量的时间特征,压测模型要反映出流量随时间变化的特征,并且变化方式尽可能拟合线上。
以图3.1为例,如果以服务单日数据为样本构造压测模型,具体做法如下:
- 将接口24小时流量样本压缩至更小时长范围,得到一个压缩后的流量样本,比如1小时共60个点
- 确保单个接口的压缩采样在某个时刻能复现该接口峰值流量
- 确保所有接口的压缩采样在某个时刻能复现总流量的峰值,或逼近总流量峰值
由此得到的压测模型既可覆盖各接口目标峰值与总流量峰值,也可有效缓解总流量放大问题。但实现中我们并没有完全采取这种方案,而是折中了一下,这在“解决方案”章节中会解释。
需要注意,因为我们引入了时间特征,如果把压测结果作为风险基准,其代价是:流量风险分析不能只关注接口流量在量上的变化,流量峰值时窗的变化也要纳入考虑。
四、解决方案
这一节的内容主要是将「风险分析」和「压测模型」(图2.1)的思路还原,围绕分析流程和实现细节展开。
4.1 流量风险分析流程
流量风险分析的详细流程如下图4.1:图4.1 流量分析详细流程
流量风险分析最关键的找到接口流量在一段时间内的有效峰值,如果采用极值法只取样本数据的最大值,存在如下局限性:
- 最大值可能是故障或者上游不规范调用引起的噪点。
- 无法获取一个完整的高峰期,以便基于高峰期数据进行压缩生成压测模型。
- 流量风险分析除了计算风险系数外,还提供高峰期的接口耗时等指标供用户对比分析,采样单点不可避免存在随机起伏。
另外,根据上文的问题分析,对于接口流量高峰期的时窗变化也是我们需要在分析时考察的内容,所以流程中的“单接口QPM寻峰”是指定位接口的高峰期时窗范围(而非单个时间点),这个时窗必须包含最大且有效的QPM。
对于线上指标的预处理-检测环节来说,具体方法如下:
预处理
缺值补0:因为是分钟采样,接口流量数据可能存在缺失,这里直接插入0值即可。
平滑:原始数据往往带有剧烈的波动导致寻峰出现偏差,在检测高峰期前需要对数据进行二次项平滑,这里采用的Savitzky-Golay算法按二次项拟合。
去噪:由于数据不可避免存在噪点或者尖刺,需要对原始数据进行去噪,流程实现中有两个环节对数据有去噪作用:预处理环节对原始数据直接去噪,检测高峰期环节由于算法上的取舍会产生去噪的副作用。为了便于描述,暂时把这两部分分别叫做硬去噪和软去噪。硬去噪:时序数据如果只考虑时间和振幅两个维度,LOF(Local Outlier Factor)是比较理想的去噪算法,实际应用时聚合多日数据可以有效去除尖刺和最大化保留原始分布。
软去噪:具体见下面「检测单日高峰期」与「高峰期时窗聚类」的描述,实践中我们发现,只靠软去噪也可以达到较好的效果,即使不用硬去噪也可以。- 检测单日高峰期
采用局部最大值算法,并根据窗口尺寸、高度等参数过滤出符合的高峰期时窗。窗口尺寸小于10分钟的峰值都会被过滤掉,所以一定程度上带有去噪的副作用。如图4.2,红色部分会被过滤掉,绿色部分会被识别为真实高峰期。图4.2 接口流量
- 高峰期时窗聚类
现实中,接口的高峰期可能会变化(如图4.3),对于多日的高峰期数据需要找出其中最典型的高峰期时窗,这里采取的方法是把每日高峰期表示成一个一维数组,比如,高峰期是11:00-12:00,则表示为[11:01, 11:02, ..., 12:00],将多日的数组聚合成单个数组然后利用KDE(Kernel Density Estimation)算法将原始数据转化成高峰期的概率密度分布,然后再对密度分布进行寻峰,过滤掉小概率的高峰期,重新找出多日内一个典型的高峰期时窗。由于聚类的作用,检测典型高峰期的同时也会带有去噪的副作用。图4.3 接口多日流量
如图4.4所示,X轴代表分钟时间刻度,蓝色代表每日的高峰期时窗,绿色是基于多日高峰期聚类出来的典型时窗,其现实意义是:该时间段可以代表大多数高峰期的分布。图4.4 多日高峰期时窗
4.2 生成压测模型
基于流量风险分析生成的峰值数据可以进一步生成压测模型。根据3.3节的分析,理想的做法是将各个接口的QPM指标进行降采样,并同时确保单接口峰值和总流量峰值同时被采样到。举个例子,原始数据按分钟采样一天的数据量是1440个点,如果目标是把数据压测到1个小时,则可按照间隔24个点采样一次的方法,将数据压缩至60个点,如图4.5所示(为了便于观察,对数据做了归一化处理):图4.5 接口QPM原始数据(左)和降采样数据(右)
左边为原始数据,右边为降采样数据(25倍降采样)。降采样后保留了接口最大QPM,从降采样数据也可以看出总流量峰值的拟合度是更好的。
同理,降采样可以不必基于24小时数据,因为有些接口在非高峰期流量很低,采样意义不大,优化方法是基于高峰期数据进行降采样,这就需要用到前面流量风险分析计算得到的接口高峰期。
这样生成压测模型的代价是:基于大家常用的压测平台,需要手动配置各个接口的压测流量,如果降采样60个点,10个接口就要配置600个压测阶段,在没有自动化的情况下人工成本太高。
所以在实现的时候采取了一种折中的方法:把降采样进一步压缩到只有2个点,有效覆盖流量峰值的同时降低压测执行的成本(因为最多只有两套压测QPM设置)。选择这个方法也是基于这样一个观察:部分服务的高峰期存在明显的双波峰,各接口峰值QPM落在不同的高峰期内。
以表3.1的数据为例,峰值时间分布如图4.6所示:图4.6 接口流量高峰期呈现双波峰
我们的目标是找出最大的两个波段,再对两个波段内的接口取最大QPM,达到将采样点从60个缩减为2个的目的。采样结果如图4.7所示:图4.7 进一步降采样的方法示意
最终的两个采样点由三个时窗中的接口分别聚合构成:
- 采样点1:(时窗1 + 时窗3)中各接口QPM最大值
- 采样点2:(时窗2 + 时窗3)中各接口QPM最大值
下面是压测模型生成流程和实现细节阐述:图4.8 压测模型生成流程
- 计算峰值时刻密度分布:采用KDE算法计算所有接口的峰值时刻密度分布,为后续计算做准备。
- 计算高峰时窗:对密度分布数据寻峰,找出最大两个波段的时窗。方法同4.1节基本一致。
- 生成压测模型:根据三个时窗聚合出两个采样点数据,一般取最大值即可。
五、总结
相较于其他时序数据异常分析方法而言,本文的方法简单直观,且理论部分(对风险的定义)有一定的经验基础,适合在常态化压测中推广实施。工程化落地时可以结合自身情况考虑以下几点:
- 需要支持哪些类型的流量?比如:实时、异步、RPC、HTTP等。
- 风险计算框架除了流量以外,是否还需要考虑其他因素?比如:接口上下游的扇入/扇出比例。
- 基准数据是否有压测数据以外的来源?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。