最新一代的阿里全链路监控系统鹰眼 3.0,同时将基础设施层、分布式应用层、业务逻辑层与客户端层进行了全链路跟踪;技术层面,鹰眼 3.0 日均处理万亿级别的分布式调用链数据,针对海量实时监控的痛点,对底层的流计算、多维时序指标与事件存储体系等进行了大量优化,同时引入了时序检测、根因分析、业务链路特征等技术,将问题发现与定位由被动转为主动。 注:本文整理自阿里巴巴技术专家周小帆在 ArchSummit 2017 深圳站上的演讲。
今天我讲的是应用分布式链路追踪技术。业界大部分的应用分布式追踪的原理源自 Google 的一篇 Dapper 系统的论文,我们的鹰眼系统也不例外。今天讲的这个议题和微服务框架是有一些关系的,大家可能听微服务相很多遍了,对微服务框架带来的好处也感同身受,比如说它提高了开发的效率,它具备更好的扩展性,这些好处大家也体会过了。可是微服务其实是一把双刃剑,微服务同时也带来了一些问题,而这些问题也就是我们的鹰眼系统需要解决的问题。
本文分为三个部分,第一个是阿里巴巴的分布式追踪系统,也就是鹰眼系统的技术实现原理、基础功能以及在阿里的使用场景。第二个想和大家分享一下这套系统背后的一些技术细节,包括流计算,存储的架构演进,我们踩过的一些坑、技术层面一些优化是什么样的,也会分享一下我们是如何对监控系统进行模块化改造的。第三个就是我们如何把一个被动的监控系统转成一个主动发现问题的系统,这一块我们其实也是在做初步的尝试和探索,在做的过程中也希望跟大家一起分享,抛砖引玉,交流一下经验。
微服务的好处已经不用多说,而微服务的坏处,大家看这张图就明白了。这张图是 2012 年淘宝核心业务应用关系的拓扑图,还不包含了其他的非核心业务应用,所谓的核心业务就是和交易相关的,和钱相关的业务。这张图大家可能看不清楚,看不清楚才是正常的,因为当时的阿里应用数量之多、应用间关系之混乱靠人工确实已经无法理清楚了。
基于微服务体系之下构建的业务系统存在的问题基本上分为四类,第一个是故障定位难,今天我们淘宝下单的动作,用户在页面上点购买按钮,这么一个简单操作,其实它背后是由十几个甚至数十个的微服务去共同完成的,这十几个甚至几十个微服务也由不同的团队去负责,这是微服务的过度协同带来的结果,一旦出现问题,最坏情况下我们也许就要拉上十几个团队一起来看问题。
第二个问题是容量预估难,阿里每年要做若干次大促活动,在以前的巨石系统当中做容量预估是非常容易的,因为我们大促时候按照预估的流量与当前系统的单机压测容量做一个对比,把所有的系统按比例去扩容就可以了。而实际上在大促的场景下,每一个系统在核心链路当中的参与度、重要性都是不一样的,我们并不能对每一个系统做等比例的扩容,所以微服务架构下的容量预估也是一件难事。
第三个问题就是资源浪费多,资源浪费多首先是容量预估不准的一个后果,同时资源浪费多背后隐含的另一个问题就是性能优化难,为什么这么说呢?我们当打开一个页面发现它慢的时候,我根本不知道这个页面慢在哪里,瓶颈在哪里,怎么去优化,这些问题累积下来,资源的浪费也成为了一个巨大的问题。
第四个是链路梳理难,我们一个新人加入阿里的时候,老板让他负责一个系统,他在这个复杂的微服务体系中,就像人第一次在没有地图没有导航的情况下来到一个大城市一样,根本不知道自己身在何处。应用负责人不知道自己的系统被谁依赖了,也不知道自己的系统下游会影响其他哪些人。
整体来看,我们所说的微服务之熵主要就包含了这四个问题。
鹰眼就是主要的目的就是解决上面所说的这四个问题,我们首先来定义一下鹰眼这个系统,它是一个以链路追踪技术为核心的监控系统,它主要的手段是通过收集、存储、分析、分布式系统中的调用事件数据,协助开发运营人员进行故障诊断、容量预估、性能瓶颈定位以及调用链路梳理。它的灵感是来自于 Google 的 Dapper。
我们来看一下它的基本实现原理。在阿里巴巴每天有超过一万亿次的分布式调用,这个数据其实也是很早之前统计的,如果在这一万亿次调用当中出现了一个问题,我们怎么去定位?看一下这个例子,系统 A 调用 B,B 调用 C,在这之后 A 又调用了 D,如果 B 调 C 出了问题的话,那么负责维护 A 系统的开发人员根本不知道问题到底出在哪里,他只知道这次调用失败了,那么我们怎么样解决这个问题?虽然现在的很多大公司都在重复造很多轮子,但还好在阿里巴巴中间件这个东西没有被重复造出两个,基础设施还是相对比较统一的。所以我们可以在一套中间件里做统一埋点,在分布式调用框架、分布式消息系统、缓存系统、统一接入层、Web 框架层的发送与接收请求的地方做统一埋点,埋点的数据能够被一套中间件在系统之间进行无缝透传。
当用户的请求进来的时候,我们在第一个接收到这个请求的服务器的中间件会生成唯一的 TraceID,这个 TraceID 会随着每一次分布式调用透传到下游的系统当中,所有透传的事件会存储在 RPC log 文件当中,随后我们会有一个中心化的处理集群把所有机器上的日志增量地收集到集群当中进行处理,处理的逻辑比较简单,就是做了简单清洗后再倒排索引。只要系统中报错,然后把 TraceID 作为异常日志当中的关键字打出来,我们可以看到这次调用在系统里面经历了这些事情,我们通过 TraceID 其实可以很容易地看到这次调用是卡在 B 到 C 的数据库调用,它超时了,通过这样的方式我们可以很容易追溯到这次分布式调用链路中问题到底出在哪里。其实通过 TraceId 我们只能够得到上面这张按时间排列的调用事件序列,我们希望得到的是有嵌套关系的调用堆栈。
要想还原调用堆栈,我们还需要另外一个东西叫做 RPCId(在 OpenTracing 中有类似的概念,叫做 SpanID),RPCId 是一个多维序列。它经过第一次链路的时候初始值是 0,它每进行一次深入调用的时候就变成 0.1,然后再升就是 0.1.1,它每进行一次同深度的调用,就是说 A 调完 B 以后又调了 D 就会变成 0.2,RPCId 也随着本次调用被打印至同一份 RPC Log 中,连同调用事件本身和 TraceId 一起被采集到中心处理集群中一起处理。
收集完了以后,我们对所有调用事件按照 RPCId 进行一个深度遍历,我们就可以获得这样的一个调用堆栈,上图中的调用堆栈实际上就是真实的淘宝交易系统里面进行下单的交易调用堆栈,可以看到这次调用经历了很多系统。但大家在鹰眼的视角上面来看,就好像是在本地发生的一样,我们可以很容易地去看到如果一次调用出现了问题,那问题的现象是出现在哪里,最后问题的根因又是发生在了哪里。除了调用异常的返回码之外,我们在右边其实还可以看到每次调用的耗时是多少,我们也可以看到每一次调用如果慢了它是慢在哪里。我们从这张图中解释了鹰眼是如何解决微服务四大问题中的故障定位难的问题,它可以通过倒排索引,让用户反查出每一次调用的全貌是怎样的。
如果我们对万亿级别的调用链数据进行聚合,是否能够获得更有价值的信息?我们可以看一下,每一次调用除了它唯一标识 TraceID 和 RPCID 之外,还包含了一些标签信息 (Tag),什么是标签呢?就是具备共性的, 大家都会有的这么一些信息,比如说这次调用它分别经历了这些系统,这些系统它每次调用的 IP 是什么,经过哪个机房,服务名是什么?有一些标签是可以通过链路透传下去的,比如入口 url,它透传下去以后我就知道这次请求在下去之后发生的每一次事件都是由通过这个入口去发起的,那么如果把这些标签进行聚合计算,我们可以得到调用链统计的数据,例如按某机房标签统计调用链,我们就可以得到每个机房的调用次数的趋势图。
这样做有什么好处呢?实际上在容量预估当中这样做的好处是非常明显的。我们来看一个例子,假如我们有一个交易下单入口标签,我们对这样的标签做了聚合以后,不光能看到单次调用它的情况是如何,还能看到将这些调用链数据聚合以后,它的总调用次数是多少,平均耗时是多少,我可以发现系统当中热点瓶颈在哪里,同时我们可以发现一些非法流量,也就是说我之前不知道的事。
比如说我们今天看到交易下单入口,订单系统耗时比以往要长了,以往都是一毫秒,但现在是三毫秒,我们通过完整链路可以看到,实际上这次交易下单应该是一个单元内的操作,但是有一部分流量因为业务逻辑的原因,本该到杭州的数据库最后跑到上海的数据库,这么一个跨集群的网络调用,实际上会导致整体的耗时增加,我们可以通过这种方式看到它热点是由什么原因引起的。同时,我们在做双 11 流量预估的时候,机器预估其实可以很容易做,因为我们只要知道交易下单入口我们预期的峰值是多少,可以按照平时的调用比例知道这次下单入口,在下游的每一个调用环节分摊到的基线流量是多少,根据基线流量来做等比例的扩容,就可以得到双 11 相对来说比较精准的扩容机器数量。
这是真实的我们系统当中的下单链路聚合结果图。除了我刚才说的一些点,我们还可以获得一些什么呢?比如说易故障点,也就是说这个系统看上去大面上没什么问题,但是偶尔它会出一些小问题。为什么我们说是易故障点,因为一旦它出现问题,下游的链路就终止了,也就是说调用对下游系统其实是强依赖的关系,那我们可以认为这个链路如果有一天下游系统真的挂了,那么整条链路的稳定性很可能出现问题,我们可以根据这些数据帮助用户提前发现这些潜在的问题。
第一节就是对基础功能的小结,它分为两部分的功能,第一个是通过 TraceId 和 RPCId 对分布式调用链的堆栈进行还原,从而实现故障定位的功能。同时通过调用链数据分析,将这些入口、链路特征、应用、机房这么一些 tag 进行聚合统计,可以做到容量预估、性能瓶颈的定位以及调用链的梳理,所有这些都包含在我们在 2012 年发布的第一版鹰眼系统中,我们接下来看一下这套系统背后的技术演进过程。
这是我们 2012 年架构,这一套架构完成了刚才我在第一章里面所说的这些功能。其实后端的技术架构看上去非常简单,但实际上是非常“重”的,我们整体的技术架构实现的大概是这样一个流程,中间件会打出调用事件流,统一在所有机器上安装的 agent 把数据上报到收集集群,然后数据进行一些简单的清洗以后,将它存到 HDFS 当中。后面我们做两件事。
第一个就是把调用链数据按照 TraceId 去做一个排序, 这样用户去查找的时候就可以通过类似像二分查找法的这种方式, 定位到 TraceId 经历的所有的调用事件。第二件事是按照 tag 进行聚合得到了一个统计报表。2012 年的这个架构初步满足了功能,但可以看到明显的问题是第一个不够实时,因为整个 Hadoop 的这一套东西是批次执行的,我们批次越大,它整体的吞吐量就越大,那么它显而易见的问题就是不够实时。我记得当时我们的延迟大概是一个多小时左右,也就是说发生了问题一个多小时以后才可以查到。第二个是不够轻量,如果我们在多个机房输出的话,每个地方都要部署这么重的一套东西,我们越来越深刻地意识到对于监控数据来说,它的价值随着时间的推移迅速地衰减,如果我们不能第一时刻给用户提供到这些数据的话,实际上我们的监控数据没有发挥出它最大的价值。
也因为这样的原因,我们在 14 年的时候完成了这么一个实时化的改造,我们把原来比较简单的、职责较为单一的负责链路调用收集的中心集群改造成了用现在大家都知道的流计算引擎去完成这件事。我们把一部分核心的,我们认为比较重要的,用户认为比较重要的数据通过流计算引擎增量计算,第一时间经过统计和聚合存储到 HBase 当中,通过大屏幕可以第一时间实时地看到用户整体的微服务之间的调用流量情况。通过实时化的改造我们也催生出了一些场景,比如说 14 年,在全链路压测的时候鹰眼成为了不管是运维人员还是开发人员非常重要的数据支撑工具,它可以实时看到流量压测下来以后每个系统的响应情况和链路之间的调用关系。同时实时流量调度与限流也是会基于这么一个调用链的情况去追溯到,比如说我发现有个下游系统扛不住了,那么我可以根据这个链路数据反过来追溯到上游,去看到链路数据的发源地是哪里,是手机淘宝还是天猫的交易。可以根据这样的链路追溯实时地去响应去做一些流量调度和限流的工作,异常链路整个的数据必须以非常高的实时性呈现给用户。
那么 2016 年我们又做了哪些呢?这套系统足够实时,但是还不够轻量,因为 Hadoop 还在那,那么 2016 年我们彻底把 Hadoop 那一套给抛弃掉,把所有的统计计算全部变到了实时引擎当中。每天几万亿查询的调用链事件存在 HBase 当中显然是扛不住的,我们是放到一个叫做 HiStore 的 MPP 列式数据库当中,这一套列式存储数据库不但能够完成原来按照 TraceId 的事情,同时它还可以通过耗时超过多少毫秒,错误码是多少各种各样的多维查询条件,把 TraceId 给查出来,更加方便用户去查找问题。
我们在实施这套流计算的过程当中遇到一些坑。第一个我们回顾一下离线计算实时计算区别,离线计算实际上是通过批次计算这么一个手段,对静态的文件进行分而治之的 map-reduce 的工作,流计算实际上是对无限的流数据切成很小的批次,对每一个批次进行的一个增量的计算,也就是说数据是不断上来的,监控数据不断产生的。以往的做法是把它存到一个大的存储当中,后面悠哉悠哉地去跑一下,计算任务挂了也没有关系。但是流计算我们不能这么做,我们必须要保持 24 x 7 的稳定性,没有办法停机维护的。因为一停机的话整个监控数据掉下来,因此系统必须具备自愈能力。解决方案其实是我们跟 JStorm 团队进行了紧密的合作, 做了不少优化,比如说把中控节点改成了高可用,自带 Exactly-Once 功能,当数据出现问题的时候,我们可以永远通过上一回滚点去回滚到上批次的数据,这样可以做到监控数据永不停息,永不丢失。
流计算第二个挑战我刚才也提到它的中间结果是尽量不落地的,那么不落地带来的问题就是说它实际上要比离线计算要难得多。举一个例子,统计全量数据,如果我们是统计一个入口 UV 这件事,其实我们在离线计算当中,这是非常容易做的。按照访问 Id 排个序,遍历得到它的 UV 就可以了,但是在流计算里面是比较难做的。这边简单提两点,一个就是全量计算到增量计算的过程,这个过程我们实际上是用了一些开源流计算库,这边给推荐大家 StreamLib,它可以很容易地帮你通过近似计算估算的方式完成一些看上去在流计算里面比较难的事情。流计算就跟离线计算一样,热点问题在离线计算当中,因为我预先知道热点在哪里,其实很容易去做数据的拆分,但是在流计算当中我们预先是不知道的。所以其实我们的做法也非常简单,我们前置了一个 LocalReduce 节点,也就是说在所有的监控数据进来之前先在本地监控一次,实际上再发到下游的时候它的流量就是均匀的,它就不会对下游的节点产生影响。
做监控系统,大家只要做过监控系统底层设施建设的其实都可能碰到这么样一个问题,也就是说那这个问题,其实我在刚开始做鹰眼的时候也是碰到比较痛苦的一个问题,就是说凌晨两点钟突然监控的曲线掉下来了,这时候我应该给运维人员发报警,还是不发报警,为什么我很难做这个抉择,因为我不知道监控的对象真的出了问题,还是监控系统本身的流计算部分出了问题,流计算系统是不是哪里卡住了,还是数据收集通道出现了问题。其实当流计算系统被应用到了监控领域中,它提出了更高的要求,一个叫做“确定性”的要求。“确定性”要求在监控系统里面,是远远大于最终一致性的,所以为了解决这个问题,我们看了一圈现在的流计算引擎提供的解法,实际上没有很好的现成方案去解决这个问题。
我们自己实现了一个齐全度算法来完成确定性的保证。它与 Apache Flink 中采纳的 Snapshot 算法有些近似,它其实是在整个流的过程当中去安插一些屏障 (Barrier),我们可以做一个假设,在所有流计算数据产生的地方,数据都是有序的。也就是说我们在收集数据的时候,通过第一探测数据源的深度,第二个在数据时间间隔出现交替的时候,比如说从 12:10 跑到 12:11 的时候去安插一个屏障随着流做传递,流计算过程中保证 FIFO,下游可以把这些屏障聚合起来,这样最终数据落地的时候,我就可以知道数据源当中有多少个屏障已经达到了最终的存储当中。通过这种方式,我们其实可以预测出当一个系统在任意一个时刻它的齐全度是多少,这样的话我们其实可以很容易得到一件事,就是说这个线掉下去了,但是因为在某一个时间点的屏障还没有完全到齐,所以我现在可以预测是因为监控系统卡住了,没有办法给下游的运维人员发报警。因为我知道现在数据还没有收齐,我没有具备确定性的阈值保证,是没有办法去发报警的。这个齐全度的算法,也就是我们在流计算当中,针对监控系统报警场景作出的确定性保证的优化。
存储层的优化也说一下,我们一开始是用 HBase 来做倒排索引的,其实我们现在在测试环境当中还是用 HBase 来存,因为简单可靠。第二个阶段也就是我们在 2012 年的时候,把这个 TraceId 进行分片,按 TraceId 排序,反过来用户通过二分查找方式进行查找。实际上我们现在是用了列式存储的技术,区别是把同一列的内容用物理相邻的方式进行存储,这样可以换来更好的压缩比。
因为我们知道对于调用链的数据来说,大部分的调用链数据都是一些非常相近的数值型类型,比如说 2ms,3ms,1ms 这样的数据,如果物理上能够连续地存储在一起的话,它的压缩比是非常高的,我们实际通过我们内部叫做 HiStore 这么一个产品, 经过这个产品的压缩以后,我们整体调用链的压缩比最高能达到 20:1,也就是说每天如果是 100G 调用链日志,我们最终压缩下来只有 5G。
指标存储,因为大家对现在对时间序列数据库的开源的实现应该也有所了解,我们对 OpenTSDB 也做了一些改造,第一个把 Proxy 层直接干掉了,降低了网络开销和一些不确定性。第二个 OpenTSDB 必须让你的数据一次算完指标,直接丢到里面存储,没有办法再进行更新的操作。而在一个真实的流计算场景下,我们知道数据有早来的、迟来的,因此业务上原本应该属于同一个时间窗口的两条数据有可能实际却在相差非常遥远的两个系统时间内到达流计算引擎,晚到的数据对于原先指标的更新的操作就很难去做。我们在基于 HBase 的时序存储的背后增加了应对这种场景的 Co-Processor,对于这些晚来的数据背后可以进行一个合并操作,变相地解决了流计算当中数据和时间窗口不对齐的问题。
下面一个议题上是我们对鹰眼进行的模块化改造,我先讲一下背景, 大家如果对分布式调用链系统非常熟悉的话,实际上它的核心还是在于 TraceId,也就是说我要查什么问题必须有 TraceId 才行,但实际业务当中碰到的问题,都不是开发人员或者运维人员发现的,而是一线的客服发现的,是业务操作人员发现的,他会跟你说某某订单号的订单有问题,某某用户发现某某商品状态不对。其实这些问题都是跟业务紧密相关的,实际上最终所有用户向我们提出了一个需求,需要能够将业务 Id 和 TraceId 双向绑定,第二个是业务指标和系统指标双向关联。这两个需求的本质都是用户需要“自定义”自己的链路。这样的需求来了,我们按照 2012 年的架构来做,就出现了一些问题。
按照我们之前所描述的架构,一种简单解决自定义链路数据的解决方法是,让所有的用户都把自定义的数据按照规范打同一份日志,所有的这些计算方式我们沿用原来的那一套,用统一的方式去处理用户的自定义链路日志,把它和另外的中间链路进行关联就可以了,实际上我们发现在阿里的环境下这套方案没法实施。第一个原因用户的日志量根本是不可控的,存储和计算的开销很大。
第二是用户的这些数据,从各个方面都充满了自定义的需求,比如说它数据来源不一定是日志,可能是队列,可能是数据库的修改的 binlog,可能是巡检、拨测的结果。数据过滤方式也不一样,它聚合的维度更是五花八门,就算是我们系统的数据,聚合维度都是不一样的。持久化方式也是不一样的,因为我们原生的方案只提供两种,一种是列式存储,它是基于写多读少这么一个场景,有的用户说我就是要毫秒级查询,我想用某某存储来存这些数据。部署方式也存在各种各样,比如说跨机房、独立部署、专有云输出的需求等。
这样的自定义链路监控需求我们是没有办法满足的。当需求无法满足时,大家都喜欢造轮子,监控系统里面的这些轮子造起来还真的蛮有意思,它有流计算,高压缩比的存储,有各种各样的数据收集,海量数据挑战。所以我们在想,与其我们订一套规范让你们强制接入,然后你回过头跟我们说我们的这套规范满足不了你们的需求,所以你们得自己造一个轮子,还不如把轮子直接给用户,让用户自己通过这些轮子造出自己的汽车来。一开始我们尝试着自己在 pipeline 里面写代码。全链路压测、线上精准回归、故障主动发现的这些场景我们就在原来那套逻辑里面去写,写完了以后发现真的是扛不住了,因为我们光是解决中间自己的问题就已经连轴转了。
后来我们发现在这套 pipeline 里虽然需求五花八门,但组成 pipeline 的各个组件其实存在大量复用的可能,所有的这些对于监控数据的计算逻辑都是可复用的。
如果我们能够把这些可被复用的逻辑变成积木块,让用户按照自己的需求去拼装监控系统的 pipeline,对用户来说是一件很爽的事情。所以我们干脆就做了一套积木块的系统,其实是把整个监控系统当中这些模块,进行了抽象和分类,比如说切分、逻辑判断、聚合、持久化、告警,还有一些用户自定义的逻辑,这一块也是借助了这套叫做 Google Blockly 开源可视化的框架,我们对它进行二次开发。用户可以直接在我们的界面上,拼出自己的监控数据处理流程。
我们来看一个例子,假如有一个交易系统,现在系统里面有一份日志,日志按照竖线分隔分别代表不同含义,它对我们的要求是,第一: 如果操作出现失败,将该业务与鹰眼调用链产生关联,这样根据交易的 ID 我们能查到底下链路到底失败在哪里。第二个是现在原生的鹰眼里虽然有交易下单的微服务调用曲线,但实际上是想知道与之对应的各个来源交易额度,想跟微服务的调用曲线放在一个视图上进行关联的对比。
我们首先定义数据来源,然后定义切分规则竖线分隔,分别这些字符是什么意思。当返回码为“ERROR”时,去记录一个鹰眼事件,这个事件是什么呢?交易下单错误,然后我把 TraceId 和用户的手机号进行关联,同时 RPCId、日期都关联上去,我们再定义一个 Aggregator 积木块来做一个聚合计算,比如按照比如入口去进行一个聚合,聚合的计算是对价格的累加,聚合后顺便存到 ODPS 阿里的大数据存储平台,可能后面这个用户还需要对这份日志去做一个离线的分析。
这一套积木块的框架,只要在页面上面简单拖拽就可以完成,同时这个框架还提供了一个所见既所得编辑模式,也就是说你把样例日志放在右上角,左边拼出你的积木块以后点一个测试,右边结果就出来了,来验证你这个积木块的逻辑是否符合预期。点击启动这个积木块就会被翻译成流计算的逻辑,把它提交到当前比较空闲的一个流计算 Slot 去执行,当然这个翻译的过程我们也是有一些优化。自动合并开销比较小的算子,比如说简单的一些切分、逻辑判断,把它压到一个 action 当中去执行。不必要的数据传递,也就是说下一步不需要的 key 和 value,其实我们帮它自动删掉了,这样用户不会进行那些没有必要的网络开销,点击启动以后这个逻辑就直接跑到我们鹰眼计算逻辑集群里面去执行了。
用户对这套积木块系统比较熟悉的话,基本上五分钟就可以完成一个自定义链路的过程。自定义链路完成后的结果在图中我们可以看到,在是鹰眼的系统调用堆栈之上,会穿插显示出在与系统调用并行出现的业务事件,另外,由于 TraceId 与业务 Id 形成了双向关联,客服人员在进行排查问题的时候,只要输入一个手机号,对应的所有系统链路就出来了。除了 Id 关联之外,业务指标和系统指标也实现了双向关联,我们可以在一个视图中看到左边是服务用量,右边是各个业务的图,在上面这张图的例子中我们可以看到这个时间点的整体调用量上去了,我们可以知道大部分是来自于淘宝业务,其他的两个天猫和聚划算的业务量是没有什么变化的。
模块化改造的结果,总结一下就是 2014 的第一个季度以前鹰眼是一个只能接受固定来源格式、按固定方式清洗、聚合、持久化的监控系统,到了 2014 年的 Q1 以后,实际上它也变成了一个自定义流式数据采集、清洗、计算、持久化的系统,它可以让用户自定义的链路数据和原来的鹰眼链路数据进行双向的关联,很快的,这套系统就在用户当中推广起来了。
截止七月前两天还统计了一下,整个鹰眼的集群线上作业数超过了 1780 了,实际上其中只有 2 个是鹰眼原生的链路统计,其他都是由用户自己配置的,可以说我们是提供了一套接入平台,是用户帮助我们构建了完整的鹰眼全链路监控体系。每天日均处理量是一个 PB 以上,秒级处理峰值是 7000 万每秒。除了原先中间那块蓝色中间件之外,我们还接入了下面橘黄色的用户业务上面的数据以及一些其他上下游的基础设施的链路数据,比如说移动用户终端、浏览器等设备的链路数据,还有数据库层面的数据,我们把 TraceId 和数据库的 binlog 打通了,当然这个也是依赖于我们数据库团队的一些改造。
上面红色的部分实际上是模块化之前,模块化之后蓝色部分就是用户自定义的链路,日志事件流、数据库事件流、终端事件流都可以通过自定义的逻辑存到指定的存储当中,同时它的这些数据一定要通过 TraceId 和我们原生的这些鹰眼数据进行关联,这样就算大家再怎么重复造轮子,最终都可以关联在一起,为一辆汽车所用,这个才是造轮子的最终目的。
我们聊一下从监控系统如何从被动报警转化为主动发现,这一转变是我们从去年开始启动的一个项目。刚才大家看到的鹰眼系统目前已经已经到了 7000 万每秒的处理峰值,在这个之前其实我们不断地做优化,2016 年以前我们自己一直陶醉在技术优化的享受当中。到了去年的时候,我们开始反思问自己一些问题,这些问题包括:我们真的需要存储每一条链路吗?就算我们的压缩比很高,但实际上随着阿里流量不断的增长,不管怎么优化存储量也是相当惊人的。第二个是就算我们有那么多数据,用户在上面配了这么多报表,我们给用户算出这么多报表,用户真的理解每个报表的含义吗?第三个是我们能不能比用户更早发现问题,在这个之前实际上鹰眼还是一个被动的用户出现问题了过来查问题的系统,我们能不能领先一步,在用户发现问题之前提前把问题暴露,给用户预知问题,甚至提示用户问题的最终原因在哪里。
我们解决问题的方法基本上也是分为三个步骤,第一个是识别,第二个是关联,第三个是定位,具体的说一下每一个步骤。
识别什么呢?识别每一个链路当中出现异常的情况。时序指标当中的异常点,也就是突然高上去那一下子,那个时间点我们要识别出来。指标中的一些离群点,比如说机房当中所有的机器,某些机器出现和其他机器不一样的行为,我们把它识别出来。
我们还能识别什么?其实今年我们做了一个挺有意思的项目,用户接了这么多的自定义日志进来,我们实际上把用户的这些日志进行了自动的归纳和整理。平时大家做应用负责人的时候可能都会有一个经历,我们常常会带着很强的“主观性”去观察我们的日志。特别是在发布的时候会盯着日志看,看日志跟以前长得差不多,这次发布好像是没什么问题,换一个人来发布的时候就完全懵了,因为他不知道日志以前长什么样子。我们其实做这件事的目的就是帮你把日志每一个种类当中的次数归纳整理出来。
当你的日志出现异动,什么叫异动,如果你的日志里面长期出现某一类的异常,这个不算异动,这个叫破罐子破摔,这个异常没有人修的,或者是前面开发的人都已经离职走了,没人知道怎么回事。异动是什么,昨天没有,今天有了,昨天每小时只有两百条的,今天每小时有两万条,这是一个异动。
再说一下识别,我们会把这些异动给识别出来,也作为识别的一个依据,和之前的所有异常点结合在一起,进行一个关联,怎么关联呢?
第一个是按点关联:同一个部署单元,比如说同一台机器上面或者同一个应用里面的这些东西可以相互关联。第二个是按线关联,时间点上靠近的,比如说这件事在某个时间点发生的,在前一分钟发生了另外一件可疑的事,这两件事其实可以关联起来。第三个是按链路关联,下面是一个链路,在 A 点上发生的事在 B 点上同一时刻也发生了,这两件事就可能有关系。最终通过关联的结果我们可以定位原因了,通过这三步识别、关联以及最后定位,我们可以定位到最终这些相关联的事到底什么是现象,什么是原因,可以把现象和原因做出有可能的关联。
举个例子,我们前段时间通过这套系统发现了一个案例,某个核心入口应用 A 的业务指标出现了小幅的波动,另外一个系统应用卖家中心日志出现了新增的异常堆栈,这两件事通过识别把它识别出来,当然在同一个时间点上识别出了一堆其他乱七八糟的异常,我们是怎么把这两件看似不相关的事情关联起来呢?
第一,在波动前一分钟,我们同时发现了做数据库拆分有分库分表的规则推送的变更,在这个变更发生前一分钟我们发现了业务指标的波动。按点关联是为什么,因为应用 B 实际上是接受到了配置变更的,那么按时间线关联,实际上这两个事情,时间线上面是非常接近的。链路关联是什么呢?应用 A 是强依赖应用 B 的,两个应用强依赖,其实是可以通过一些基线去得到。那么最终我就可以认为这一次的分布分表配置变更是引发 A 应用指标波动的高嫌疑原因,实际上我们通过识别出来异动新增的异常堆栈,人工验证以后确实如此。也就是说这一套识别加上关联再加上定位的手段,我们认为有可能是行之有效的,我们也准备将这套系统继续开发下去,今后有什么新的进展也可以跟大家一起同步。
总结一下之前讲的一些东西,实际上我之前讲的这几部分内容的顺序,也就正好吻合鹰眼版本的演进史,第一版本实现基本的链路定位以及指标分析的能力。第二个是平台化,通过积木块的方式把这些轮子给用户,让用户自定义我们的链路,用模块化平台化的手段让所有用户把不同的数据接入进来,共建我们的全链路系统。第三个版本实际上是全息排查,同时我们对流计算以及底下的一些存储做了优化。
实际上如果我们去看整个监控领域的话,基础运维层面是大概五年前大家都做得滚瓜烂熟的事情,再往上是应用层以及链路层的监控,这些监控通常是通过调用链的数据或者调用链聚合的数据来完成的。业务层面,我们希望通过业务和应用系统层面双向的 TraceId 的关联以及指标的关联来完成一件事,就是业务出现了问题,我能第一时刻定位到底是哪个系统出了问题,哪个系统的变更可能导致业务层面给用户感知的问题。
17 年我们的鹰眼系统也正式在公有云向用户开放,目前 EDAS 中的鹰眼包含了对阿里中间件 (Aliware) 体系之上构建的微服务系统提供了开箱即用的链路监控能力。而更加完整的自定义链路监控功能、前端浏览器监控以及其他相关的 APM 功能在阿里云的 ARMS(Application Realtime Monitoring System) 上面也开放了,如果将 EDAS/PTS/ARMS 配合在一起使用的话基本可以让一个企业在微服务治理、容量规划、应用链路监控以及全链路压测方面做到和阿里相同的水准。同时我们内部也在做一些智能诊断相关的一些事情,包括业务链路自动梳理和自动根因定位的能力,在合适的时机也会在云产品上开放给大家使用。
作者介绍
周小帆,阿里巴巴高级技术专家。8 年金融 + 电商 + 中间件工作经验,目前就职于阿里巴巴中间件技术部,整体负责中间件的监控与数据化运营工作,同时为阿里商品、菜鸟、安全、国际站等多个业务部门提供了完整监控方案。参与了阿里近五年来监控体系的建设及演进。阿里云“业务实时监控 (ARMS)”技术负责人。兴趣是业务架构、分布式计算与数据存储技术。