货拉拉全链路监控体系的落地与实践
导读: 随着业务发展和交易复杂度的不断提升,生产问题的提早发现和快速排障成为一个重要的话题,今天将与大家分享货拉拉是如何落地全链路观测。
今天的介绍会围绕下面五点展开:
-
监控演进史
-
货拉拉监控体系架构
-
监控埋点"弯道超车"
-
全链路 Trace 建设
-
可视化建设 -- "所见即得"
分享嘉宾|曹伟 货拉拉 架构师
编辑整理|KS icbc sdc
出品社区|DataFun
01/监控演进史
首先和大家分享下互联网行业监控的演进史。
首先我们看一下整个监控行业的演进历史。
较早的是 2002 年 eBay 的 CAL 这么一个全链路监控产品,在 eBay 内部也被大面积的运用,是他的前资深架构师吴其敏主导研发的。
然后是 2010 年谷歌的 Dapper,这是内部做的一个监控产品并没有开源,不过发表了一篇 Dapper 论文,是后面很多监控产品的鼻祖,大部分都是基于 Dapper 论文的实现和演进;同年出现一个商用产品 Datadog,功能比较全面,是行业一流的标杆产品。
2011 年吴其敏从 eBay 离职来到美团,开发了 CAT,相当于 CAL 的 Java 版实现,同时做了很多的优化、迭代,将 CAT 在开源领域发扬光大,在整个开源监控领域受到的欢迎程度比较高,整体的覆盖率也相当的高。
2012 年 Twitter 开发出了 Zipkin,相当于 Dapper 的开源实现。同年阿里也在搞他的 Eagleye(鹰眼)监控,韩国的一个 Pinpoint 也出现了,值得说的是他是基于字码增强技术来实现监控数据埋点,有个亮点是他的埋点非常的细致,细到底层方法维度,同时这也是个缺点,因为埋点多多少少会有点性能损耗,其次他会导致单个 Trace 数据的膨胀。
2014 年,饿了么开始搞 EMonitor,他对用户的一个排障思路的引导,以及用户体验都做的非常好,是我用过所有监控里面最好的一个产品。货拉拉的全链路监控体系的很多思想都是借鉴于他的,比如说我们的"所见即所得"的思想是如何运用到监控可视化建设中的。
2015 年,华为的吴晟开发了 skywalking 并且将他开源,近些年发展还挺好的,加入了 Apache 基金会,在当前的监控领域还是比较受欢迎,特别是他在微服务体系这块支持的比较好,所以受到很多中小型公司的追捧,甚至一些大公司也在用。像我们货拉拉的全链路 Trace 服务初期也是基于他结合货拉拉场景进行深度定制开发的,期间也经过几次大的架构升级,这个在后面会详细的说。
最近的是 2016 年 Uber 的 Jaeger 是基于 Go 语言来实现,滴滴内部也在使用,目前也开源了。
接下来看一下货拉拉演进史,共分为三大阶段:
(1)监控 1.0 时期
各个业务团队独立维护一套 Prometheus 监控体系,也没有全链路 Trace 监控,缺乏统一标准,更别说治理能力了,夸张一点说,很多时候是客服接到投诉后把问题反馈给业务,业务才知道系统出现问题,然后再去排障,所以效率极其的低。
(2)监控 2.0 时期
我们开始制定标准并逐步统一监控,进行监控治理,推进业务基础监控全覆盖,得益于字节码增强技术帮助我们实现业务"零代码"改造快速接入基础监控,实现监控"弯道超车",Java 核心服务监控 100% 覆盖。同时我们也自研一些基础监控页面,结合着 Grafana 大盘来满足日常的监控场景。我们在中期也开始从 0 搭建全链路 Trace 服务,自研智能告警体系。
(3)监控 3.0 时期
这个阶段我们主要针对各个监控领域专项做深度迭代和打磨,比如 Trace,我们实现错、慢、核心服务完整全采样,在不影响业务排障体感的前提下,将整体存储成本降低 60%。另一方面,由于历史原因 Metric、Trace 和 Log 都是各个团队独立维护的,缺乏较强的关联性,整体监控体系的价值得不到最大化,所以我们在这个阶段将他们和业务之间的指标做一个闭环的打通,来提高业务整体的排障流畅度。同时我们重新自研基础监控和业务大盘页面,给业务提供更智能便捷的大盘自助配置能力。
(4)监控 3.x 时期
目前完成了指标瘦身,自研 Prometheus 集群 manager,将 Trace 存储从 HBase 切换到 KV 存储,存储成本降低 90%。也落地了一部分根因分析、服务的调用拓扑的功能。
--
02/货拉拉监控体系架构
这是货拉拉 3.x 的架构图。从下往上,最底下方是 Prometheus,它负责从多个维度采集数据,并上报数据到中间的 VictoriaMetrics 集群,这是一个时序数据库,主要做一些计算分析,供上层展示和智能告警。右边的 ES、HBase、Kafka 是为上层 Trace 服务提供存储和计算能力。
整体的架构就是 Metric 体系由 Prometheus 加上 VictoriaMetrics 搭建而成,Trace 部分直接使用 Skywalking 的 Trace 模块,再结合货拉拉的场景做了深度定制,形成了全链路 Trace 服务的架构。
货拉拉的监控埋点体系完整覆盖了客户端、服务端、基础设施、DB 等各种组件的不同版本,能满足货拉拉所有业务的埋点诉求,这是通过字节码增强埋点实现的。
传统的埋点需要做侵入式改造,比如说需要对 SDK 做二次封装或者修改源码,同一个产品的不同版本都需要区别处理,维护成本很高,限制较多。而字节码增强通过对代码的拦截进行埋点,维护成本很低,业务零代码改造,一键接入,非常便捷。
--
03/监控埋点"弯道超车"
1. 字节码增强技术介绍
下面介绍下字节码增强技术。
字节码增强技术可以分两块来谈,第一块是如何修改,这是字节码增强的框架 要做的事。第二块是修改完的字节码如何生效?这是通过 Java Agent 实现的。
2. 字节码增强框架选型
框架方面,主流框架有 ASM、Javassist 和 Bytebuddy。
ASM 是字节码增强的鼻祖,不少工具都是基于它二次开发实现的,但是它的学习槛较高,需要熟悉偏底层的汇编指令和 Class 文件的数据结构,通过硬编码手段编写增强逻辑,Debug 也不太不友好。
第二个是 Javassist,是日本一个教授的开源产品,比 ASM 要简单一些,但是依然需要硬编码,Debug 不友好。
第三个框架是 Bytebuddy,非常容易上手,完全遵循 Java 编码习惯,可以 Debug。
货拉拉对这三种框架进行了比较,最终选定了 Bytebuddy。
3. 字节码增强生效机制说明
修改后的字节码是如何生效?主要是通过 Java Agent 技术实现的。我们知道 Class 文件最终会被加载到 JVM 内存待使用,字节码有 2 个契机被修改:
一是加载的过程中触发拦截,触发字节码修改动作,最后将修改后的字节码数据加载 JVM 得以生效;
二是已经完成加载的 Class 数据也是可以从 JVM 中被获取、修改后覆盖原数据达到增强效果。
下图是 Java Agnet 的官方原理图,它有两个类,Agent.class 和 Mytransform.class,Agent.class 中有一个 premain 方法,顾名思义就是在 main 方法执行之前执行,他做的事很简单,就是往 JVM 里面注册一个 transform,大家注意看 transform 方法的入参,有一个字节数据,这就是原生的 class 文件,通过内部字节码增强工具的修改,可以增加一些自定义的逻辑,之后以返回值的形式交还给 JVM 被加载到内存里实现修改效果。
举个简单的例子,一个 Base 类和一个 process 方法,想要达到的效果是:在 process 执行前后各加一行 start 和 end 的输出。硬编码一般就是在方法里面写死,下图展示的就是字节码增强,拿到这个 base 类,然后找到它的 process 方法,然后 insert before,insert after 加两行输出就 OK 了。
--
04/全链路 Trace 建设
1. Trace1.0 架构
1.0 的时候,直接使用原生 Skywalking 的架构,抽出其 Trace 模块,数据存放在 ES 里。但是随着业务接入,发现无法支撑全量货拉拉的业务数据,而 ES 的水平扩展的成本较高,其存储架构不适合放 Trace 这种大数据量的数据,因此我们进行架构 2.0 的升级。
2. Trace2.0 架构
架构 2.0 做了两个大的改动:一是把数据的采集和消费做了拆分 ,采集到的数据发送给 Kafka,由 Kafka 消费做数据分析和存储;二是**数据存放在 HBase 中,将 ES 作为索引使用。**这个架构可以支撑百万 TPS 的 Trace,也可以通过水平扩容支撑更大体量的业务接入,但是 Trace 体量过大存储成本过高的问题也急需解决。
3. Trace3.0 架构
Trace 存储的难题通过更精细化的 Trace 采样解决。
Trace 的采样方式有很多种,这里我们先介绍下 Skywalking 原生的常规采样方式,其实是一种常规、无差别的采样手段。那么他是怎么做的呢?我们先简单了解下 TraceID 的结构,他主要由 3 段组成,最前面是 PROCESS_ID,简单理解为 UUID,中间是个线程 ID,第三段是我们比较关心的毫秒级时间戳加上一个自增序列,原生 Skywalking 取这个时间戳的毫秒级的千位数据段来制定采样规则,比如说 Trace 满足毫秒数据段小于 100 就保留,从而来达到 10% 的采样率效果。这种常规采样简单易实现但仅凭 TraceID 无法筛选出有价值的数据。
所以我们提出另外一种方案,就是更深入一点,从 Trace 详情数据入手。我们可以看到这个 Trace 的结构是由多个 Span 组成,Trace 维度包含 APPID、Latency 信息 Span 维度又包含耗时、类型等更细粒度的信息,根据不同的 Span 类型设置不同的阈值,比如远程调用 SOA 耗时大于 500ms 可以认为是慢请求,而如果是 Redis 请求,阈值就需要设置小一些,比如 20ms,通过这种方式可以将慢请求规则更精细化,同样可以通过判断 APPID 是否为核心服务来过滤保留核心服务的 Trace 数据。
4. Trace3.x 架构
3.x 架构中,做了两个大的提升。
一是**使用自研了 KV 存储取代了 HBase,大幅降低了存储和计算成本。**HBase 会做一些额外排序合并的动作,非常耗 CPU,货拉拉自研的一个 KV 存储方案,针对 Trace 场景优化了它的 LSM Tree 的合并规则和频率,切换后吞吐率得到了提升,并且存储大幅降低,计算成本降低到 90%。
二是保障了 Trace 链路的完整性。
上面解决了差异化、精细化采样,但还有个棘手的问题,就是在采样时如何保证链路的完整性?解答这个问题前,让我们先了解下什么是链路完整性?
这里举个例子,如图 现在有一条远程调用,经过 ABC 和分支 AD。这里有个前提就是 ABCD 它是 4 个不同的服务,独立异步上报 Trace 数据没有严格的时间顺序。在 B 调用 C 出现异常时,我们能轻松识别到并将 B 和 C 的 Trace 数据段采样到,只保留 B 和 C 的这种情况,称为部分采样 。但是在实际的一个排障过程中,我们还需要 A 和 D 这条链路数据作为辅助信息来支持排障,所以最好的方式是把 ABCD 都采样到,作为一个完整的异常链路保存起来,这称为完整采样。
接下来看下我们是怎么解决的?简单说我们基于 Kafka 延迟消费 + Bloom Filter 来实现完整采样。
比如说我们 Kafka 有两个消费组,一个是实时消费,一个是延迟消费,实时消费每条 Trace 数据时会判断下是否满足我们的采用规则,如果满足就将 TraceID 放在 Bloom Filter 里,另外一方面延时消费组在半小时(可配置)开始消费,从第一条 Trace 数据开始消费,针对每条 Trace 数据判断 TraceID 是否在 Bloom Filter 中,如果命中了,就认为这条 Trace 应该被保留的,从而能做到整个 Trace 链路的完整采样保存。
此处有一个细节需要说明,图上的 Bloom 使用的是 3 主 3 从 1C4G 的 Redis 小集群,通过一个取巧的方案实现了百万 QPS,用 Redis Bloom 构建出一个本地 Bloom,第一次从远程查询并构建了一个本地 Bloom,后续都是走本地,这样就极大降低了对 Redis 集群的查询压力,这也是这么低配的 Redis 集群能支撑这么高 QPS 的原因。
基于以上架构,实现了错、慢、核心链路的完整采样。
--
05/可视化建设 - "所见即得"
接下来,这一章节主要带大家看看我们在监控可视化建设中如何运用"所见即所得"思想的。首先看下我们的基础监控大盘页面,左边主要有一些 Exception、Http、SOA 以及核心基础服务的一些指标数据。我们的一大亮点就是所有的曲线都可以通过点击展示出具体的 Trace 页面。针对 QPS、RT 标高曲线可以点击查看 Trace 详情,进一步排查异常点。
要实现所见即所得的顺滑的排障体验,就必须打通独立维护的 Metric、Trace、Log 和业务数据形成闭环。那么我们是如何打通的?其实重点就是建设对应数据结构的映射关系。我们可以从左往右看映射关系图。
首先是 Metric 数据,包含 APPID、Name、Tags 和时间戳等关键信息,这些信息都可以作为 Trace 列表查询入参,到 ES 里查到对应的 Trace 基础信息列表,再通过 TraceID 从 HBase 中查询 Trace 详情,包含整体调用链的 Span 列表,最终构建出Trace调用链展示到前端。
同时 Trace 和 Log 之间通过 TraceID 建立很强的关联关系,业务在接入监控后每条 log 都会自动添加 TraceID 数据以此来建立强关联。
再看右边的业务数据,我们支持业务代码中自由埋点,以 Trace Tag 的形式将业务数据(OrderID、UserID、DriverID 等)埋到 Trace 数据中,这样就将业务数据和具体的 Trace 关联上了,同时我们支持根据 OrderID、UserID 等业务参数查询对应的 Trace 列表,这样就把各方面数据串联起来了。
在这个这套体系出来之前,业务大部分都是通过日志里面查关键字来进行第一步的异常发现和排障,效率非常的低。现在首先是从 Metric 指标页面去看哪些 QPS、RT 飙高,然后点击曲线看 Trace 链路,查看异常调用栈,如果还看不到有用的信息再点击跳转到 TraceID 对应的日志里面查看。整个排障思路和手段更高效、规范,相当于重新定义了业务的排查思路。
--
06/总结
首先从价值层面,无论是自研还是基于开源解决方案深度改造或是外购商用的全量监控体系,都会很大幅度提升全局稳定性,一方面可以帮助快速定位问题,另一方面,对微服务治理也有很大的帮助,ROI 很高。
其次,从发展的角度,后续会做一些自研的存储,比如说已经完成从 HBase 到自研 KV 存储的切换,后面也会自研一个时序数据库存储 Metric 数据。另外正在研究、落地根因分析方面的工作,来提高排障效率,还有指标和告警的联动等等。
最后,货拉拉的整个平台都是基于开源产品搭建起来的,因此也希望有机会可以回馈开源社区,此次分享也是回馈社区的一种方式。
今天的分享就到这里,谢谢大家。
分享嘉宾
**