dbaplus社群

异常定位准确又高效!去哪儿网根因分析实践

去哪儿网一直在构建并完善企业级的 apm 系统,包括链路追踪系统 qtracer、监控系统 watcher、异常统计分析系统 heimdall 以及公司事件平台。

其中qtracer是公司自研的链路追踪系统,可以用来快速获取系统间的调用信息。为了能够在监控报警时,快速检索出指标相关联的trace,我们进行了metric和trace的结合。如下图所示,不同于业界常用的label标签关联,我们使用了trace的context进行关联,达到的效果是在某个时间点,一个带着trace信息的请求从入口进来,经过了方法foo(),而foo()中记录了指标foo.access.time,那么通过指标foo.access.time以及对应时间段,便能够查询到此条trace信息。并且在报警发生时,qtracer系统会自动提高trace的采样率。

Image

watcher 是公司的一站式监控平台,可以很容易的获取报警信息、指标数据和指标单机数据、以及基础监控数据(CPU、网络等)、DB 数据等。heimdall 是公司的异常统计分析系统,能够支持分钟级别的异常统计以及某个时间段内的同比环比。公司事件平台收集了全司应用的各种类型事件和变更,比如发布、配置、压测、k8s 事件等。

公司 apm 系统的构建和完善,为根因分析系统的数据获取提供了坚实的保障。

2、整体框架设计

根因问题定位,业界有许多优秀的探索,比如eBay链路分析、腾讯决策树方法等思路。下面我们进行简单介绍。

1)eBay 链路分析

链路分析,是通过分析 Trace 数据中的断链和错链问题,以定位出故障根因的方法。

Image

如上图所示,通过对图上面部分3个核心链路的归并,可以很清晰的看到,在 userCheckout 这个链路中 calculateMoney 的调用接口由"coupon" -> "couponV2"后,对于 createOrder 来说,调用量上升了20%,延迟降低了5%,因此,倾向于定位是 coupon 结点的问题。

2)腾讯决策树方法

决策树是具有强解释性的有监督分类模型,其本质是一颗由多个判断节点组成的树,如下图所示,就是一个典型的两层结构:基于天气判断是否要出去玩。

Image

决策树的生成,就是将一个数据集基于某个特征将其分成 N 类,循环划分子集,直到满足一定条件(子集数),或者子集中数据无法再继续拆分。决策树本质就是 if-else,也就是说,我们根据一定的条件来对数据集做了分类,当一个数据落入某个节点时,它是有原因的,其原因就是它的生成路径。

决策树来做根因分析的大体流程:

  • 特征工程:选择合适的数据,并将数据数值化

  • 模型训练:利用sklearn等工具训练决策树模型

  • 基于训练结果做根因分析,或做新数据预测分析

  • 针对分析的结果做校验和优化

以上两种方法,对于根因分析有很好的借鉴意义。但根因分析系统的实现,必须要结合公司的具体实际,包括基建、技术栈、业务场景等,以打造出符合去哪儿网自身的根因分析平台。

去哪儿网根因分析的构建思路,主要是模拟人的思维。如下图所示,通过调研机票、酒店、公共和服务的开发同学在问题排查时的经验,我们发现大多开发同学在收到报警时,会通过报警查看trace调用链路,以获取当前应用以及调用的下游应用,然后结合自身的经验,判定大概是哪几个应用的问题,然后针对这些应用,去查看日志、事件等,最终定位问题。

Image

我们基于调研结果,并结合业界的实践,抽象出了去哪儿网根因分析系统分析定位的核心思路,即是:定位范围 → 异常断言 → 关联挖掘 → 剪枝排序 → 结果输出。在具体实现上,是以报警/指标为入口,通过分析关联trace,获取异常应用,并对异常应用进行多维度分析,对结果汇总、剪枝、排序后输出。

Image

上图为根因分析系统的整体架构图,主要包括以下6个模块:

  • api/listener:服务入口层,支持api触发分析以及listener监听报警消息触发分析

  • orchestration:分析编排控制层,负责控制整个分析过程以及控制整合分析结果

  • analyzer:具体的分析器,包括链路trace、运行时runtime、事件event、日志log、中间件middleware、以及可扩展的extender

  • data processor:分析结果处理模块,对分析结果进行聚合、权重判定、剪枝、排序

  • feedback/learning:反馈及学习模块

  • base data:基础数据层,负责获取及聚合分析模块所需的各种数据,比如event事件、日志信息、报警信息、指标数据等

3、核心模块介绍

1)trace分析模块

Image

Trace 模块的作用是根据报警或者指标,查询出对应时间段内的 trace 信息,并根据 trace 是否异常、T 值(T 值是去哪儿网大客户端的一个概念,是为了区分各业务线服务的,在请求的时候,会带一个参数 qrt 称之为 T 值。不同的 T 值,对应的业务也不同)、相似度等筛选策略进行漏斗式过滤,获得一定数目的 trace,之后根据筛选得到的 trace 定位出异常应用。Trace模块是整个根因分析系统的核心,也是分析结果能否准确的前提。

难点分析:

  • 时间段的确定

对于监听报警所触发的分析,trace 时间段的选择是个需要首先考虑的问题。时间段过短,有可能导致相关 trace 未查询到;时间段过长,就会包含过多的无用 trace。

我们的开发同学在配置报警时,都是对一段时间内指标数值进行阈值判定,比如3m>4(连续3分钟大于4),2%5m>7(5分钟内有2个点大于7)。因此我们会根据当前报警所设报警规则的时间段,再加一个特定的时间窗口(比如3分钟,在第一个点满足报警阈值时,异常便已经产生了,向前推一小段时间,以防止 trace 遗漏),作为查询时间段。

  • trace的筛选

在前文我们提到过,在报警发生时,qtracer 会提高 trace 的采样率。因此即使我们选定的时间段比较短,仍会查询到大量的 trace(几千甚至数万条)。而如果每次根因分析都对如此规模的trace数据逐条进行拓扑构建并详细分析,会造成巨大的计算成本浪费,并且会极大延长分析耗时,因此需要对查询到的 trace 进行筛选。

trace 筛选的核心思想是漏斗式的筛选,首先根据 trace 是否异常进行分类。qtracer 在记录链路的过程中,如果某次调用判断为超时,或者 http/dubbo 返回状态码异常,便会将此次调用标记为异常,并将 trace 标记为异常 trace。若筛选出的异常 trace 量级仍然很大(比如大于500),那么我们继续根据 trace 的相似度进行进一步的筛选。

对于非异常 trace,首先根据T值进行筛选,因为不同的 T 值,对应的业务场景也不同,调用链路也不同。对T值相同的 trace,根据相似度筛选出 n 条数据,为了提升相似度的判定速度,我们将图进行降维,根据特定的规则将图构建为字符串,转换为字符串的相似度算法。

  • 异常应用的判定

在排查问题的过程中,异常 trace 要比非异常 trace 的作用大的多。对于筛选到的异常 trace,首先进行拓扑构建并标记异常应用,之后对构建的 trace 拓扑进行归并,某个异常应用出现次数越多,则越倾向于是此应用导致的此次问题,其权重也就越高。如下图所示,在 traceA、traceB、traceC 三条调用链路中,应用 C 都出现了异常,那么我们倾向于认为是应用 C 导致的此次问题。

但 qtracer 当前只能标记出调用超时、调用状态码等异常,对于调用的返回结果中的业务状态码并不会进行判断。因此某些无异常的应用,也是有可能导致此次问题。我们在重点关注异常 trace 的同时,也会以非异常 trace 作为辅助。对于非异常 trace,我们对当前报警所属应用节点下钻 n 层拓扑,拿到拓扑应用后,根据应用的报警浓度等策略进行异常应用判定。

Image

2)应用纬度分析模块

概览

Image

trace 分析模块负责定位出异常应用,而应用纬度分析模块则是对异常应用进行多维度分析。我们通过对业务线开发同学定位问题的过程以及公司故障数据进行调研分析,抽象出了应用分析的四个维度,分别是

runtime:运行时分析,主要包括应用的单机实例(kvm、容器、宿主机)分析、业务指标单实例分析、JVM 分析如 full gc 等

middleware:中间件分析,目前主要是应用相关的 mysql 和 redis 分析

event:事件/变更分析,目前主要分析的是发布、配置变更、压测等,事件分析支持按部门配置,每个部门都可以单独配置自己关注的事件类型

log:日志分析,通过 heimdall 的异常日志统计能力,筛选出一段时间内的同比环比超过配置阈值的异常类型;并以筛选出的 trace id,从 es 查询出的异常日志作为补充

下面我们将挑选 runtime 和 middleware 两个较复杂的模块进行介绍。

runtime

Image

runtime 是应用的运行时异常分析模块,目前主要分析单机实例、jvm 指标、单机业务指标三个部分。其中单机实例包括单机基础指标和宿主机指标两个部分,单机基础指标对于应用不同的实例类型会分析不同的指标,宿主机的健康状况也会影响到单机实例,因此我们找到应用的实例对应的宿主机,并分析宿主机的健康状态。JVM 指标目前主要分析是 full gc 和 young gc 数据,我们根据专家经验,会对每个 java 应用添加 gc 报警,并支持应用 owner 修改阈值。当分析过程中发现有相关报警时,便会将报警汇总到分析结果。

业务线很多应用有大量的实例,很多达到了500以上。某些报警或者故障是应用某个或者某几个实例导致的,我们报警系统配置的指标是所有实例聚合得到的一个总指标,因此我们会根据配置的业务指标,获取对应时间段内的单机指标数,纵向对比,使用离散度算法比如3西格玛算法,尝试定位出异常单机实例。如下图所示,应用有五个实例,而实例4在对应时间段内都偏离其余四个实例,那么我们倾向于认为实例4有问题。

Image

middleware

Image

middleware 是应用的中间件分析模块,目前主要分析应用的 mysql 和 redis 实例运行状况。如上图所示,模块首先根据应用名,调用 tc 同学提供的接口,获取对应的 mysql 和 redis 的 namespace 及宿主信息,然后进行异常分析。在 DBA 同学的帮助下,我们分别确认了 mysql 和 redis 相关的核心报警。

mysql:

  • pmp-check-mysql-status-pc:pxc 节点状态,告警说明 pxc 不可用

  • pmp-check-mysql-status-size:pxc 集群没有可用节点,业务无法访问

  • pmp-check-mysql-status-fc:pxc 集群触发流控,整个集群变慢甚至不可用

  • pmp-check-mysql-status-threads-running:mysql 线程异常,实例负载过高或者有慢查询导致堵塞

  • pmp-check-mysql-processlist:mysql 线程异常, 实例负载过高或者有慢查询导致堵塞

  • pmp-check-mysql-replication-delay:主从同步延迟

  • pmp-check-mysql-replication-running:主从同步失败

  • pmp-check-mysql-status-connect:连接数已满,无法连接

  • check_mmm:集群状态有问题,节点下线

  • pmp-check-mmm-vip:mmm 集群的 vip 连通性,告警说明 mmm 集群 vip 不通,业务无法访问数据库

redis:

  • q-check-redis-cpu-usage:redis cpu 告警

  • q-check-redis-memory-usage:redis 内存告警

  • q-check-redis-latency-port:redis 延迟告警,比如 redis 大 key 时有可能导致此报警

检测到对应的 mysql/redis 的 namespace 或者宿主有以上核心报警时,则直接将报警汇总到分析结果;若无报警,则分析慢查询指标数量,若增长趋势异常,则将异常信息汇总到分析结果。

3)权重体系模块

权重体系在整个根因分析过程的剪枝排序环节非常重要,直接决定最终推送结果的准确性。

目前根因分析系统主要抽象出了四类权重,以对分析模块分析出的结果进行综合计算。主要包括应用权重、静态权重、动态权重和强弱依赖权重。下面我们将从what(是什么)、why(为什么)、how(怎么做)。三个方面来阐述四类权重。

应用权重:

what:应用权重表示在整个调用链路中,当前异常应用影响报警/故障的概率的大小。

why:比如我们对多条异常trace进行分析,发现应用B多次被标记为异常应用,那么我们倾向于认为,是应用B大概率导致此次问题,计算得到的应用B的权重就会很高。又或者对于一条调用链路A->B->C→D,如果当前报警的是应用A,那么根据专家经验,应用B对A的影响要比应用C对A的影响要大,因此我们认为应用B的权重要比应用C要高。

how:目前应用异常权重分两部分计算,异常trace的应用权重通过trace归并计算,比如每重复出现一次,权重增加0.1等,并且异常应用也要综合考虑和当前报警应用的拓扑联通性以及连通层级。而对于非异常trace,主要是根据应用距离当前报警应用的距离计算,类似于欧几里德距离,距离报警应用越近,权重越高。

静态权重:

what:静态权重也就是经验权重,我们总结了2022年上半年的故障原因分布占比,来设置根因分析每个维度的权重比。

why:其实这种静态权重代表了去哪儿网自身的业务特点,比如因发布或者配置变更导致的故障占比50%,说明在去哪儿网发布或配置变更是一个相对比较危险的动作,如果再次发生故障,且我们在事件分析中定位到了异常应用有发布或者变更,那么我们就会倾向于认为这次问题大概率是此次发布或者变更导致的。

how:人为统计故障原因分布占比,并配置到应用配置中。需要注意的是,这个静态权重不是一成不变的,需要每隔一段时间,就根据新的数据调整一次。我们的反馈和学习模块,也会自动根据故障原因分布和用户反馈结果,进行静态权重的调整。

动态权重:

what:动态权重,我们也称为root case权重,根据各个原因的严重级别来对自身进行升级,以避免真正的根因被淹没掉。

why:静态权重只能是一个大概的推测,并且已经归一化了,起到的更多是default设置的作用,提供一些参考意义。比如一个报警或故障,我们分析出链路中某个异常应用有一些调用超时日志,且同时有mysql/redis实例连接失败,那么按照专家经验来看,mysql/redis实例连接失败是一个严重级别很高的原因,因此mysql/redis实例连接失败这个case,便可以自身升权。

how:目前动态权重主要是依靠专家经验,以及case的严重程度,比如磁盘使用率100%,IO使用率100%,DB/redis挂掉,CPU使用率超过90%等,都可以动态升权。

强弱依赖权重:

what:强弱依赖权重其实有些类似应用权重,强弱依赖里明确标明了,A应用的 m1接口依赖B应用的 m2接口是强依赖还是弱依赖,根据此信息可以断定,B应用的异常尤其是m2接口的异常对A应用尤其是A应用的m1接口影响的概率。去哪儿网强弱依赖关系,是通过混沌工程进行强弱依赖演练得到的。

why:强弱依赖是已经人为或系统标记了 A 应用到B应用是否是强依赖或若依赖,这是一个非常重要的信息,比如 A 同时依赖 B、C两个应用,但是根据强弱依赖信息,我们知道B应用对A应用是强依赖关系,而C应用对A应用则是弱依赖关系,那么当A故障时,我们同时发现了B和C两个应用此时都有异常信息,那么此时可以倾向认为B的异常导致了A故障,B的权重大于C。

how:目前强弱依赖标记的是接口维度,在根因分析过程中,根据trace分析拿到当前异常的接口,则可以根据API搜索当前应用的此API的强依赖,如果没有被标记,那么可以回归聚合,将接口维度的强弱收敛到应用维度的强弱,比如A依赖了B的3个接口,2个弱依赖接口一个强依赖接口,那么我们就可以认为B是A的强依赖,按照这种该逻辑来收敛应用级的强弱依赖,强依赖的权重大于弱依赖。