
最近看到《去哪儿技术沙龙》公众号上的这篇分享,写的很接地气。
原文地址 https://mp.weixin.qq.com/s/AhXvRlrV4GoR3cLsARzAFQ
2025年10月20日 18:00 北京
文章概览
作者介绍:
杨赞,去哪儿旅行DBA,负责MySQL、Redis运维和慢查询系统、巡检系统开发。
传统的巡检系统都是基于固定阈值来判定指标是否异常,一般为了防止产生过多的指标异常信息,这种阈值设置的都偏高。虽然这种方式也能发现异常,但是场景过于单一,无法感知指标的动态变化,例如在业务逐渐进入高峰期时,数据库的QPS、负载或者数据体量也都是逐渐升高的,等达到日常的异常阈值再处理,时间上会比较紧张,而如果能提前发现数据库核心指标的变化以及变化幅度,那DBA就能提前介入分析和处理,也减小了因为负载过高或者容量紧张带来的业务异常的可能性。
近年来,机器学习和人工智能技术快速发展为时序监控提供了新思路,机器学习可以从历史数据中找到自身的规律,识别出常规之外的行为。去哪儿网DBA将时序监控数据的异常检测运用到日常巡检中,通过智能分析来发现数据库指标的动态变化异常。
整体思路如下:

MySQL服务器磁盘使用量、服务器内存使用量、Redis内存使用量等绝大部分监控指标的监控曲线特征基本是相同的,也就是正常情况下趋势平稳(平稳上升/下降,或者变化趋势不明显),这类监控指标在底层可以共用一套异常检测逻辑,通过不同的参数来调整各自的特征。上面再封装一层不同的业务检测逻辑进行定制化,减少误报。本章节以MySQL磁盘使用率检测为例进行介绍。

报警信息如下:

3.1.2 平稳趋势异常检测实现
平稳趋势异常检测使用的DoubleRollingAggregate算法,是一种窗口检测算法,其主要原理如下:

在监控曲线上创建两个滑动窗口,每个窗口覆盖n个监控点(可设置),两个窗口依次从左向右滑动,右窗口的平均值-左窗口的平均值的结果生成一条新的曲线,也可以根据需求计算每个窗口的中位数/最大值/IQR等指标,得到的数据曲线如下:

新曲线小于0的点说明是原曲线在下降,新曲线大于0的点说明是原曲线在上升,曲线上绝对值越大,那么变化(上升/下降)的幅度就越大。
可以根据新曲线大于0/小于0的点的个数占总点数的比例,来判断整体趋势上升或下降的程度。
窗口大小可调节,窗口越大,结果越平缓,IQR检测时越能消除噪音,负面影响就是可能漏掉一些异常点。
根据磁盘增长的特性,检测逻辑如下:
a.在磁盘使用率小于设定阈值时检测到有变化异常,但是异常时间范围内有对应实例中的表在执行DDL操作功能,且工单执行时间范围和异常时间范围吻合度较高,则不记为需要关注的异常现象。
b.在磁盘使用率小于设定阈值时检测到有变化异常,但是有数据在往其主机上进行迁移,迁移执行时间范围和异常时间范围吻合度较高,则不记为需要关注的异常现象。
有些实例没有设置max_binlog_files参数,只设置了保留7天的binlog,在有归档任务时可能会产生几百G的binlog,导致磁盘异常上涨,这种场景会把这台服务器上的所有实例信息展示出来,包括参数配置,binlog大小等信息,方便快速定位问题。所以在磁盘使用量异常报警消息中增加了一个辅助报表协助DBA定位磁盘使用异常的原因。报表内容如下:

稳定性算法通过调整一些参数就可以检测到长期缓慢增涨的场景,例如下面监控图是MySQL扫描行数的监控图,由于业务新上线了一个sql,随着表数据量增加和执行频率增加,导致实例扫描行数长期缓慢上涨,,在造成严重的慢查询前需要检测出这种异常来。

具体实现如下:
先过滤出近一天最大扫描行数大于设定阈值的实例。对于这种长期增长的检测可以简化为对比每天高峰期时间扫描行数异常变化的检测,取每天高峰时间内的一个监控点,连续取近30天的监控数据,然后对这30个监控点进行平稳性异常检测,如设置窗口大小为5,每个窗口取最大值,计算两个窗口的差,通过判断新曲线正值的比例,即可判断出这种增长异常。
为什么不简单的使用扫描行数大于某个阈值就视为异常呢?首先各个实例对应业务场景不同,阈值的设定无法进行较好的统一,设置较低告警信息太多,设置较高会导致漏报。如果和趋势判断结合,就能弥补固定阈值检测导致的漏报问题,并且可以提前发现风险进行介入处理。
对于实例的QPS,MySQL的扫描行数,Redis内存使用这些指标,有些业务场景下是具有周期性变化规律的。但是最近一段时间突然脱离了周期性的变化范围或幅度,这种异常情况也需要进行识别,本章节以Redis周期性内存使用量异常检测介绍符合周期性变化特征的异常检测。
以下是一个Redis实例内存使用率监控数据:

从监控图上可以看出该Redis实例的内存使用有两个明显特点:
对于这种情况我们重点关注的是连续周期的变化趋势是不是一致的,最近的变化和之前的同周期变化有无较大差异,而对于单个周期内的增减变化不予以重点关注。通过周期性异常检测算法检测结果如下,红色的监控点是检测出来的异常点。可以看出最近一个周期的变化量明显高于前面的。

报警消息如下:

周期性检测通过SeasonalAD算法实现,此算法通过pipenet连接多个数据转换器和异常检测器来检测出异常点,具体流程如下:

a.数据转换器deseasonal_residual: deseasonal_residual通过ClassicSeasonalDecomposition算法去除周期性的规律特性。基于自相关函数(ACF)获取周期长度,经典的周期性分解假设时间序列是由趋势项 (trend)、季节项 (seasonal pattern) 和噪声(即残差) 三部分加和而成的。此步骤通过移动平均计算并移除趋势成分,通过计算去趋势化后序列在多个季节周期上的平均值来提取季节模式,最终返回残差序列。 返回的结果如下:

b. 异常分析:异常分析通过由两条检测链路进行检测,然后取两个链路结果交集。
链路1 sin_check:用于检测上升的异常或检测下降的异常,或者都检测,通过参数side设置。例如side = "positive",那么sin_check输出的异常点是所有deseasonal_residual大于0的点,如下:

这样再和链路2的iqr_ad求交集后,就只剩下iqr_ad上涨的异常点了。
链路2中有两个算法,首先是abs_residual,用于计算deseasonal_residual结果的绝对值,转化为绝对值是为了在使用四分位距法检测异常时便于计算,转化为绝对值后的结果如下:

通过abs_residual转换的结果再通过iqr_ad(InterQuartileRangeAD)的四分位距法计算离群点,检测结果如下,其中红色为离群点(异常点):

最后将链路1 sign_check检测出来的异常点和链路2 检测出来的异常点求交集,得到最终结果如下,其中红色部分为我们需要关注的异常点:

在原始监控图标识出上面检测到的异常点,得到的最终结果如下:

3.2.3 检测调优
在使用周期性变化异常检测之前,首先要识别出要检测的指标是否具有周期性变化的规律,通过机器学习模型训练,对近n天的监控数据进行分解周期特征,计算自相关性来获得周期信息。一般情况下这种周期性规律不会有很大的变化,所以在初始节点将需要进行检测的指标全部遍历检测一遍,然后将符合周期性的集群信息记录到元数据库中,后面再定时更新相关元数据。在进行周期性变化异常检测时只检测数据库中有相应记录的集群即可。另外对于有周期性变化规律的集群指标,在进行平稳性异常检测时可以很好的进行过滤或者设置特殊的阈值。
在使用周期性变化异常检测时还需要有其他检测标准进行联合判断,否则可能会导致很多无效的异常信息,在满足下列其中一项条件的情况下,对于Redis内存使用量检测,即使周期性变化监测到有异常行为也被视为安全范围内的异常波动,不需要关注和进行消息提醒:
这种情况是指在很短时间范围(瞬时)内,监控指标突然发生较大幅度的变化,下面以MySQL服务器CPU水位变化检测和QPS异常检测为例分别介绍。
正常情况下,MySQL的CPU水位在20%以下,常规报警阈值设置为50%,在达到报警阈值之前,如果cpu水位突然上涨并持续一段时间,说明业务有不合理的请求,需要关注。 如下面所示的监控曲线,在16:25时间点突然上涨约一倍的量,并一直持续保持在30%的使用率,对于这种瞬时突变的场景应视为异常现象:

图4-1-1
报警信息:

水位突变检测使用LevelShiftAD检测算法,它使用了多个转换器和检测器,通过pipenet连接起来。

链路1先用DoubleRollingAggregate(diff_abs)算法进行数据转换,同时使用两个滑动窗口从左向右滑动,计算两个滑动窗口的中位数的绝对差值(Absolute difference),产生一组新的时间序列。再通过InterQuartileRangeAD(iqr_ads)四分位距法计算离群点。
diff_abs将图4-1-1监控数据转换成新的时间序列,形成的曲线结果如下:

将diff_abs转化后的时间序列作为iqr_ads的输入,通过iqr计算出异常点,异常检测之后的图示结果如下,红色点是异常点:

链路2同样先通过DoubleRollingAggregate(diff)算法进行数据转换,不同的是这次是计算的两个窗口中位数的差值,产生另一组新的时间序列,形成的曲线如下。然后再通过ThresholdAD(sign_check)算法检测异常点。

sign_check作用和周期性检测中的sign_check一样,用于区分方向性,这里只检测水位上升,所以所有正数都是异常点。 图示结果如下:

链路1和链路2的检测结果求交集,获得最后的结果,检测出水位突然上升的边缘,图示如下:

通过静态参数过滤掉一些没有必要的报警,例如如果cpu的最大值小于阈值,则不算异常。如果最近几个点的平均值小于阈值,则不算异常,因为已经恢复了,没必要重复报警。一些BI类业务设置高阈值。
QPS虽然波动很大,但一般都是向上突增,即使有向下的波动其变化幅度也不应该很大。如果一个实例的QPS突然降低到几乎为零,然后又快速恢复,那这种情况是存在异常的。本章节介绍检测QPS掉0的场景。注意这里说的掉0是突然降低到很低的水平,不一定全是0。
在大事务提交时,由于写binlog耗时长,将Binlog Events写入Binlog文件的过程必须要串行执行,只有一个事务写完了,另外一个事务才能执行,这时可能导致写操作掉0。低版本的MySQL是单线程复制,从节点应用大事务时,也会导致从节点QPS掉0,并伴有主从延迟。如下监控所示,在约16:30时刻QPS突然大幅度降低,然后又快速恢复。

通过异常检测算法检测出来的效果如下(红色点是异常点):

这种指标突然掉0的异常情况使用IQR(Interquartile Range,四分位距)检测可以快速地把异常值筛选出来。IQR是统计学中衡量数据分散程度的指标,特别适合处理非正态分布或存在异常值的数据。通过聚焦数据中间50%的分布范围,规避极端值干扰,是数据分析中稳健的离散度度量工具。
很多异常检测都是把原始数据转换后,通过IQR进行的离群点检测,是一种基本的离群点检测算法。
IQR 描述的是数据集中“中间50%”的数据范围,详细概念如下:

若数据超出 [𝑄1−𝑐1 * 𝐼𝑄𝑅 , 𝑄3+𝑐2 * 𝐼𝑄𝑅] 范围,则视为异常值。
举例说明:
# 把监控数据从小到大排序后,数据如下data = [5,7,10,15,19,21,21,22,22,23,23,23,23,23,24,24,24,24,25]len(data) # 19 # 散列图import matplotlib.pyplot as pltplt.scatter(range(len(data)),data)# 直方图 是频率分布plt.hist(data)

# 第一个四分位数Q1 = df.number.quantile(0.25,interpolation='nearest')Q1 # 19# 第三个四分位数Q3 = df.number.quantile(0.75,interpolation='nearest')Q3 # 24#四分位间距IQRIQR = Q3-Q1IQR # 5# 假设c1设为1.5,第一个四分位数以下,并检查较低的离群值。小于这个值的数为异常点Q1 - 1.5*IQR #11.5
# 假设c2设为1.5,第三个四分位数以上,并检查较高的离群值。大于这个值的数为异常点Q3 + 1.5*IQR # 31.5正常范围是 [𝑄1−𝑐1 * 𝐼𝑄𝑅 , 𝑄3+𝑐2 * 𝐼𝑄𝑅] = [11.5,31.5]
所以超出[11.5,31.5]范围的点认为是异常点,5,7,10这3个点是小于下限点异常点,没有大于上限的异常点。

在实际场景中很多集群的QPS波动幅度很大,且没有明显的规律性,从数学意义上的离群点中过滤出我们需要关心的异常点是此检测项中的重点。
首先我们需要明确,这种场景下我们只想检测出突然掉为0的这种变化比较极端的情况,所以c2设一个很大值,不检查突增的异常点,c1设一个合理值,这样可以检测出突然掉0的异常点。
因为这种掉0的情况持续很短,检测监控点的时间范围一般不能太长,否则容易丢掉异常或者有误判,所以从prometheus获取监控数据的step设为了15s,提高采集精度。如果由于故障导致QPS长时间掉0,那么其他核心指标也会出现异常,数据库报警会发现这种情况进行报警,这种情况不在该异常检测场景中。
在做这种突然掉为0的极端变化异常检测时,还需要考虑周期性和先突增又突降的情况:
a.周期性下降

上图这种周期性掉0的情况不应该算做异常,需要剔除掉。检测思路如下:
计算近一段时间是否具有周期性规律,如果有周期性规律,计算出一个周期的长度n(一个周期里有n个监控点),计算最近连续的2个低水位的异常点的平均值a,获得这2个点对应的前一个周期同一时刻的监控平均值b和前两个周期对应同一时刻的监控平均值c,对比a和b的绝对值,a和c的绝对值,因为正常情况下异常点的值都很小,所以这里用绝对值进行比较,没有用比例。如果绝对值差别不大,说明此次掉0是具有周期性的,不算做异常。
从历史曲线中计算出周期长度的思路如下:
对周期性下降检测处理后的结果如下,这种不会发送巡检异常消息,这里为了展示处理效果。

b.水位上升然后下降

上图这种下降后持续保持低位,可能是有任务导致的,IQR检测时取的近30分钟的数据,但定时任务可能执行很长时间,所以这里取近10个小时的监控数据,如下:

需要把这种水位上升之前qps本来就很低的场景识别出来,这种场景不应该算作异常。水位异常变化的场景基于LevelShiftAD算法检测,和MySQL服务器CPU水位检测算法一样,检测结果如下。

这样就获得了levelshift变化的异常点,但这种异常点有很多,例如上图中水位高的这部分中又有一个峰值(qps大于1000的这部分),需要进行处理。
从iqr返回的异常点中获取最近一段连续的异常点,求最小值min_iqr,这个值就是最近的qps掉0时的值。
从levelshift返回到异常点中从右向左遍历,获取每组连续的异常点,剔除最右边的一组(这组是qps下降的点),获取剩下的异常点的值,因为窗口法滑动时有滞后性,所以还要获得每个异常点左边的window个点的值(window为一个窗口覆盖多少个点),求最小值min_ls,比较min_iqr和min_ls的绝对值,如果水位增长前的点和iqr最近的异常点的差值在很小的一个范围内,或者min_ls比min_iqr还小,则认为水位前后的qps一样,不算做异常。
为了调试方便,把详细过程发送到消息里:


目前我们已经在线上成功部署了CPU/磁盘/内存/表大小/QPS/扫描行数等十几个关键指标检测项,系统报警准确率在80%以上,显著提高了巡检效率,并有效减少了常规报警数量。
在算法策略方面, 我们认识到不同指标具有各自的数据特征,无法依赖单一算法覆盖所有场景。因此,我们通过对指标进行相似特征归类,提取其数学意义上的共性,对同一类指标采用适配的算法体系。目前通用算法已覆盖约 80% 的检测需求,其余部分则通过定制化逻辑精准补充。
我们将算法与业务场景深度结合,主动识别并过滤由 DDL 工单执行、数据迁移等不定期操作引起的指标波动。这类业务行为属于正常范畴,不应触发异常报警,从而有效降低误报率。
在阈值管理方面,采用动态与静态相结合的策略:动态阈值通过机器学习自动学习指标历史规律,实现敏感度的自适应调整;静态阈值则作为硬性边界,提供兜底保护,防止极端情况下的漏报。
针对报警通知,我们实施了多维收敛机制:纵向收敛基于时间窗口,对连续出现的同一报警进行合并与消息更新;横向收敛依托关联分析,对同一集群下多个实例的相同报警,或同一业务组内多个集群的同类报警进行聚合处理,从根源上避免告警风暴,提升告警可读性和可操作性。
在推进智能异常检测系统演进的过程中,我们坚持由简入繁、循序渐进的策略:首先从单指标异常检测入手,逐步拓展到多指标联合分析,最终实现自动化的根因定位。目前,去哪儿网DBA团队已在单指标检测方面覆盖绝大多数运维场景,并针对特定集群和业务需求完成了定制化处理,有效提升了检测的适用性与准确性。
当前,我们正稳步推进多指标联合异常检测的探索与实践,旨在通过指标间的关联分析进一步提升检测覆盖率和精准度。未来,系统将逐步承担更多自动化诊断工作,帮助DBA减少重复性巡检负担,使其能够将精力聚焦于更高价值的技术优化与紧急故障处置中。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。