本文讲述了云音乐全链路跟踪系统的设计思想、实践路线以及在技术选择与功能迭代方面上思考总结。
为什么我们在三年前要立项做这个系统?当然是我们遇到了服务问题。当时最明显的问题就是,随着我们的服务化拆分越来越多,服务间调用关系越来越复杂,我们已经完全无法跟踪系统的调用关系,某个链路出了问题,我们无法追溯,甚至那时候我们也没有一个统一的日志查询平台,我们需要不断的登录到不同的主机上去查询日志,可以想见当时的开发人员的生存环境是多么的恶劣。所以,我们迫切的需要一个全链路跟踪的系统,我们认为有了全链路跟踪,我们就能快速进行问题定位,异常排查,错误处理,性能优化了。
在系统建立之初,我们总体上有两个大的方向,一个方向是全局视图,即从整体上知道全局服务的依赖关系拓扑。第二个方向是能查询任一请求的具体调用链路。
我总共分五章来阐述全链路跟踪系统的整个发展历程。每个章节包含了我们的目标及反思,以及我们对于为什么这么做系统设计的原由。
首先第一章,调用栈与监控。
基于我们初期对链路系统的理解,调用栈和基础监控指标是必须的,因为只有有了调用栈关系,才能解决我们上下游调用无法串连跟踪的问题。基础监控是我们发现问题的底层保障。所以,我们就朝这个目标开干了。
在做之前,我们评估了当前世面上的大部分开源APM。这些系统大致情况如上,各有优劣,但后续我们还是走了了自研的道路。
为什么走上这条道路?这与我们的目标、我自身的认识、我的期望有莫大关系,同时,也与云音乐的一贯风格有一点关系。
最后一栏Pylon,即云音乐全链路监控系统,这个是我们目前已经选择的方案及达到的能力,一并参与比较出来。
首先,我们做这个系统的底线是什么?即链路系统消耗要低,不能对业务系统产生过多影响,链路系统是锦上添花的事情,绝不能成为业务的包袱。在云音乐当前的用户体量下,trace日志量一定是巨大的,所以,实时的异步上传trace日志一定会消耗大量网络带宽,同时,链路collector端的稳定性也会对业务系统产生一定影响。要保证轻量级,保证可控,那本地异步输出日志是最切实的保证。所以,我们就采用了写本地日志并由DS收集,最终写入Kafka的方案来进行trace收集,从而摒弃掉了业务端直接上报的方案。当然,对于这种决策有重大影响的是另外一个系统的支持,即阿里的鹰眼。我们认为云音乐的体量已经不再适合直接上传trace日志了。
我们将所有的trace采集集成在中间件(包括RPC及各种存储系统)这一层,来屏蔽掉对业务方的耦合。我们定义了我们自己的私有协议。当然,最近大部分开源APM系统都支持了opentracing协议。后续如果有必要,我们也会考虑支持上。支持的好处在于,我们可以使用开源的采集客户端来丰富化、多样化我们对各种组件的trace支持,同时使用自己的后端处理系统来提供强大的数据分析能力。
当然,我们基于应用名、链路名、主机IP等多维数据查询需求,使我们选择了ES作为底层存储系统。为了获得实时的数据聚合能力,我们首次提出使用Flink来解决流式处理问题。
这样,所有组件都齐备了,现在就需要考虑如何设计我们的系统了。
这就是我们的系统架构,一看就能明白,此处不再详谈。
我们到底如何开始我们的设计呢?
想象一下我们的用户,他该如何开始我们的系统?他应该从最宏观的方向入手,例如,他应该知道某个应用需要关注,因为这个应用在某段时间不健康了。然后,深入到这个应用内部,他发现了哪个RPC/API有问题,接着,他可以发现是某些主机的问题,也可以发现是所有主机都存在某个问题。最终,他需要确切知道到底哪儿引起了问题,这时候,他要找到一系列请求,来查看问题出现在哪个调用栈上。
于是,我们提取了整个流程,来绘制我们的数据视图。主要包括:应用->链路->主机->请求。这是一个层层递进,由表及里的过程。
当然,在数据的粒度方面,我们定义了一些基础粒度,如5s、1m、5min、1h,基于这些基础粒度,我们可以实现任意时间区间,任意粒度切换。怎么说了,选择一个时间区间,我们自行判定与组合该区间内包含的基础粒度指标,达到随意选择的目标。同时,我们多粒度的设计也能高效查询返回。例如查询一周的数据,我们可以使用1h的粒度作为基础粒度进行聚合返回。查询5h30min的区间大小我们可以组合若干1h粒度及若干1min钟粒度进行聚合。这样的设计对用户查询是非常友好的。
其次,由于是流式处理,多粒度的聚合即减少了采集数据延迟带来的整体数据量的误差,同时也解决了查询实时性的问题。
各维度,各粒度的基础监控
基于traceId的链路查询
如上图所示,我们在第一章节就完成了我们的基础目标,达到了基础监控与调用栈查询的能力。但是,在使用过程中发现,达到这一步还很不够,因为虽然有了基础监控,但是在某一异常时间点关联的异常请求太多了,并且异常服务也非常多,随机选择一个异常请求是完全没办法判定root cause的,因为很多异常请求都是由于线程池满导致了timeout错误,但我们却无法从茫茫异常请求中找到导致线程池满的根源。
于是,我们需要对服务进行全量梳理,我们需要发现内部的关联关系,我们认为,有了一个全局的异常拓扑图,我们就能从拓扑图上找到异常的root cause应用了。
现在进入我们的第二章。服务梳理,指标强化。
我们有了那么多监控数据,有了那么多具体请求调用数据,我们发现用户使用成本很高,甚至无从下手,那为什么会出现这种情况?这是我们设计之初忽略掉的问题,即线上服务太多了,服务之间的依赖太复杂,异常的发生是相互传递的,当某个服务出现异常时,它的所有依赖服务也会出现问题。我们抓取到的任何一个异常请求,都可能由于上层依赖的服务同时异常而请求中止掉,从而我们无法真正找到底层直接异常服务,即root cause服务。
那该如何解决这个问题?必须全面梳理服务关系及链路关系,必须强化各项指标的监测。
这一阶段,我们需要整理服务的调用关系,需要对性能优化进行指导,业务最关心的异常需要直接展现,同时提供全环境支持。
具体来说,服务梳理,首先梳理应用的直接依赖应用,应用的调用关系,同时,需要梳理应用与其他应用产生了哪些链路调用及依赖。
链路梳理,即梳理链路间的调用关系及依赖关系,链路的调用拓扑及链路强弱依赖等。
有了这些关系数据,我们现在终于知道每个服务、链路它所处的位置、它的拓扑关系,可以完整绘制出所有服务拓扑大盘了。全局服务中任一服务出现了问题,我们基于拓扑大盘,就能快速定位到最底层的root cause应用及root cause链路了。
这在故障定位方面前进了一大步。我们可以看一下具体异常时后的拓扑表现:
全局异常拓扑、应用依赖关系、应用链路关系
如上图所示,我们在此阶段可视化了服务间的调用依赖关系,能够快速清晰的展现应用与链路间杂乱的网状结构。
链路调用关系(统计视图)
当然,我们也提供了丰富的统计分析数据。这些数据有一些是基础性的,如总量、均值、区间振幅;有一些是高级的,如直方图、调和平均、离散度、百分位统计等。
这些数据能够全面而完整的呈现出服务在某一阶段的质量状态。
基础统计指标
离散度、响应直方图、百分位统计
然后,我们打通了网易内部及云音乐体系中的其他系统,包括提供基础主机监控的哨兵,依赖cmdb业务分组建立了业务视图,打通了配置中心获取服务中间件组件,关联了RPC元数据来提供每个请求的RPC更多内部信息。每一个请求与日志关联,同时,我们也提供出了SLO与GoTest的外部分析通道。有了这一些组件的数据关联,我们基本建立了对所有系统的分析循环。
基于自身的大数据,及外部系统的数据依赖,我们完成了许多的基础功能建设。包括业务分组视图、全局搜索、慢响应分析、健康色谱、集群视图、异常链路与日志、组件配置等。这些功能特性极大的丰富了数据的使用场景,提高了整个全链路系统的问题分析能力。
各种分析能力分模块集成在应用分析、链路分析、主机分析、服务分析等独立分析页上,以下展示应用分析页:
应用分析
全环境最大化输出了系统对业务的使用场景与能力,完整覆盖了开发使用的各个环境。
有了这些丰富的指标,有了对服务的梳理,我们也能定位到root cause应用了,但在实践中发现,这还很不够。
1、由于我们缺乏中间件数据(redis, mysql, memcache等)采集,从而导致我们丧失了更具体的异常定位能力。我们只知道某个应用出了问题,但是不知道应用出了什么问题。
2、实践中发现线上环境远比想象的更复杂。既使在整体服务质量较高的情况下,线上几百个服务的异常及超时都在随时发生。整个全局异常拓扑都呈现出大片红彤彤的景象。按照这样的拓扑图,既使真正异常发生时,干扰的异常也足以淹没掉真正的root cause应用,更别提更细致的定位了。
所以,在第三章,我们的总体目标即为自动化服务诊断,能够发现服务确实存在某些问题;及在此基础上达到异常定位的能力。
我们的服务诊断,即是分类服务中存在的问题。首先,我们需要把所有服务异常按严重程度进行划分,于是我们建立起了服务的健康质量分类,包含:
前两类服务都是不需要作为关注目标的服务,基于此分类,基本可以过滤掉绝大部分有异常但不影响服务质量的应用/链路了。后两类服务是需要进行关注的服务,因为他们的异常量已经达到历史统计的极端区间,虽然并不表明他们一定存在某些问题,但也需引起开发者的注意。
不仅如此,在线上爆发大规模异常时,告警与危险类别即可作为当前故障对全局影响程度的可靠指标,因为这两个类别已经非常明确且高效的告诉了开发者,这些服务已经深受故障影响,异常/超时达到甚至超过了历史统计极值。
在服务诊断中,我们还有一个指标,即抖动。抖动是通过对响应时间的正态分布统计中划分的置信区间,来判定当前响应是否存在毛刺。我们通过对抖动次数的长期统计,建立起对统计值的百分位等级划分,来判定服务的不稳定程度。通常,我们将达到99%百分位的抖动划定位高危险抖动,并由此推断服务存在极大的问题需要进行排查。
服务健康等级、链路健康等级、抖动识别
那我们有了服务诊断,该如何定位到具体的问题了?
这是一个很难的问题,需要有大量的数据,这些数据需要包含所有上下游的所有信息;同时,对所有数据进行分析筛选过滤,来发现真正引发问题的root cause。
虽然哨兵包含了非常丰富的指标,但由于我们当前无法对哨兵数据进行有效同步分析,于是我们选择了另外一个方向,即基于服务异常日志来进行全量分析。
所以:
这样,当异常发生时,在大量繁杂的异常中,我们基于这套标签及评分机制,能够较快较好的获取问题定位的快速判定。
当然,我们还对非常重要的异常进行了特异化的信息提取,如RPC超时异常。我们提取了其中包含的RPC key信息及IP, Port,并建立了对该数据的实时统计。这样,我们基于此数据能非常明确的知道服务的质量情况,以及哪些主机存在问题。当然,现在的局限性在于,这个机制对应用异常非常准确,但对中间件异常,无法获取到有效的主机信息(因为当前缺少中间件异常日志数据)。
基于标签化的异常分类日志排序展示
异常日志中提取的RPC信息
现在,我们可以建立起全局的诊断视图了。包括全局的健康指标,流控降级应用、线程池异常的服务、优化的异常拓扑,同时包含同比环比等各项数据。其中,线程池、流控降级数据基于日志标签进行获取;高危抖动来源于抖动分级;异常拓扑进行了优化,只展现告警及危险级拓扑关系,标签云来源于全局日志标签,同环比、趋势图皆来自于实时流统计监控指标,服务分类来源于整体应用的服务分级。
综合这些数据,我们既能判定异常,又能判定影响范围,既能获知故障程度,又能排查故障来源(如果发生中间件组件异常,只能通过异常分类进行排查,无法通过RPC信息获取于具体的主机,除非中间件异常信息包含了主机信息)。于是,我们构建起了完整而又实时的服务健康图谱。
全局及应用异常依断、异常拓扑优化
如上,优化过的异常拓扑图明显比原始的异常拓扑清晰很多,主要原因在于优化后过滤掉了很多干扰的异常数据,突出了与当前故障更相关的异常。
我们的目标是,与其他平台全面整合,推动数据与业务共享,建立监控生态闭环。
助力业务,主要分为两个部分,第一,基于抖动检测的实时报警;第二,基于异常/性能分析的分类报表。
当前我们大力推进全链路系统与日志,SLO,网关、存储,RPC,静态化等各项服务打通,做到数据的高度融合。同时,我们已经完成了云音乐内部其他服务的数据输出,在质量、效率、稳定性、性能方向建立了强大的能力图谱。全链路跟踪系统已经从单一的服务系统转变为云音乐内部基础设施系统,从面向业务开发扩展到面向服务,达到云音乐内部整体生态闭环的目标。
以上还在在当前采样率很低的情况下的数据(当前采样率不是固定的,是以要采集到多个请求来计算每个API需要配置多大采样率,默认采样率是千分之一)。
所以,它的价值,在20年以及在未来,一定会不断强化与渗透到云音乐系统建设的各个方面。
当前的目标是提供更加智能化的问题分析定位能力,补全我们在存储监控上的短板;同时trace客户端支持云音乐全量组件,最后能在业界获取一定的技术传播。
在存储方面,我们基于OWL基础数据建设存储系统的基础架构可视化,建立所有存储的应用依赖关联,同时获取各存储的性能监控实时指标,最后建立存储组件的服务质量量化标准。
当前trace客户端主要基于RPC及mylife公共组件。对于异步线程池,HTTP、HBase等都需要业务调用trace API打点,耦合性较强。同时,由于使用私有协议,无法与业界通用的标准协议打通,后续考虑支持标准的openTracing协义,同时对于各组件模块,业务方可以使用业界最全的pinpoint trace客户端进行打点采集。
最后,我们要进行智能化平台建设。基于深度学习与神经网络的预学习建模,来获得更高的准确性分析,包括异常分类问题,时间序列预测问题等,来解决流处理实时性及数据完整性的问题;同时,基于预建模型,我们能更方便的对数据进行有效性判断,对于非重要数据,直接不进行存储来节省存储空间。
作者简介
郭元华,网易云音乐技术中心公共技术组技术负责人,java技术专家。
领取专属 10元无门槛券
私享最新 技术干货