GinoBeFunny

分布式调用跟踪系统调研笔记

随着微服务架构的流行,服务之间的调用关系愈加复杂,如何跟踪它们之间的调用关系以及对依赖进行分析显得更加重要。而分布式调用跟踪系统就是为了解决这个问题而生的,本文为调研学习已有DST系统的学习笔记。

分布式调用链跟踪系统设计目标

  • 低侵入性 – 作为非业务组件,应当尽可能少侵入或者无侵入其他业务系统,对于使用方透明,减少开发人员的负担;
  • 灵活的应用策略 – 可以(最好随时)决定所收集数据的范围和粒度;
  • 时效性 – 从数据的收集和产生,到数据计算和处理,再到最终展现,都要求尽可能快;
  • 决策支持 – 这些数据是否能在决策支持层面发挥作用,特别是从 DevOps 的角度;
  • 可视化才是王道。

Twitter zipkin

SEE:http://zipkin.io/pages/architecture.html
SEE:https://github.com/openzipkin/zipkin

  • Zipkin 是一款开源的分布式实时数据追踪系统),基于Google Dapper的论文设计而来,由Twitter公司开发贡献。其主要功能是聚集来自各个异构系统的实时监控数据,用来追踪微服务架构下的系统延时问题。应用系统需要进行instrument以向Zipkin报告数据。Zipkin的用户界面可以呈现一幅关联图表,以显示有多少被追踪的请求通过了每一层应用。
  • Zipkin以Trace结构表示对一次请求的追踪,又把每个Trace拆分为若干个有依赖关系的Span。在微服务架构中,一次用户请求可能会由后台若干个服务负责处理,那么每个处理请求的服务就可以理解为一个Span。当然这个服务也可能继续请求其他的服务,因此Span是一个树形结构,以体现服务之间的调用关系。
  • Zipkin的Span模型几乎完全仿造了Dapper中Span模型的设计,Zipkin中的Span主要包含三个数据部分:基础数据(包括traceId、spanId、parentId、name、timestamp和duration,主要用于跟踪树中节点的关联和界面展示)、 Annotation(用来记录请求特定事件相关信息)、BinaryAnnotation(提供一些额外信息,一般以key-value对出现)。
  • Zipkin架构组成:Zipkin的收集器负责将各系统报告过来的追踪数据进行接收;而数据存储默认使用Cassandra,也可以替换为 MySQL等;查询服务用来向其他服务提供数据查询的能力,而Web服务是官方默认提供的一个图形用户界面。

Zipkin架构

淘宝EagleEye

SEE:https://www.slideshare.net/terryice/eagleeye-with-taobaojavaone
SEE:http://jm.taobao.org/2014/03/04/3465/
SEE:http://www.myroute.cn/show.php?md=%2Fali%2FeagleEye%2FeagleEye%E6%BA%90%E7%A0%81%E5%88%9D%E6%8E%A2.md

  • 日趋复杂的分布式系统:RPC、消息通讯、数据库分表分库、分布式缓存、分布式文件系统……
  • EagleEye是基于日志的分布式调用跟踪系统,脱胎于Google Dapper论文。其核心是调用链,每次请求都生成一个全局唯一的ID(TraceId),通过它将不同系统的“孤立的”日志串在一起,重组还原出更多有价值的信息。目前已覆盖了淘宝主要的有网络通信的中间件,包括前端请求接入Tengine、分布式会话tbsession、远程服务调用框架HSF、异步消息通讯Notify、数据库中间件TDDL、分布式缓存Tair、分布式文件系统TFS、特定功能的客户端如搜索等;
  • 处理过程:在前端请求到达服务器时,应用容器在执行实际业务处理之前,会先执行EagleEye的埋点逻辑(类似Filter的机制),埋点逻辑为这个前端请求分配一个全局唯一的调用链ID。这个ID在EagleEye 里面被称为 TraceId,埋点逻辑把TraceId 放在一个调用上下文对象里面,而调用上下文对象会存储在ThreadLocal里面。调用上下文里还有一个ID非常重要,在EagleEye里面被称作RpcId。RpcId用于区分同一个调用链下的多个网络调用的发生顺序和嵌套层次关系。对于前端收到请求,生成的RpcId固定都是0。当这个前端执行业务处理需要发起RPC调用时,淘宝的RPC调用客户端HSF会首先从当前线程ThreadLocal上面获取之前EagleEye设置的调用上下文。然后,把RpcId递增一个序号。在EagleEye里使用多级序号来表示RpcId,比如前端刚接到请求之后的RpcId是0,那么它第一次调用RPC服务A时,会把RpcId改成0.1。之后,调用上下文会作为附件随这次请求一起发送到远程的HSF服务器。服务的逻辑全部处理完毕之后,HSF在返回响应对象之前,会把这次调用情况以及TraceId、RpcId都打印到它的访问日志之中,同时会从ThreadLocal清理掉调用上下文。
  • EagleEye的使用场景:调用链跟踪(与异常监控集成);链路分析(容量规划、调用来源、依赖度量、调用耗时、调用并行度、调用路由情况);透明数据传输(本身需要传递TraceID等上下文信息、透明传输业务数据);
  • EagleEye的实现之埋点和输出日志:中间件创建调用上下文生成日志埋点、放在ThreadLocal对业务透明、在网络请求中传递;TraceID、RpcID(Dapper里的SpanID)、开始时间、耗时、调用类型、IP、处理结果等;性能影响改进(利用无锁环形队列做日志异步写入来避免hang住业务线程、调节日志输出缓冲大小控制每秒写日志的IO次数、做采样和开关控制);

埋点和输出日志

调用链示意图

  • EagleEye的实现之收集和存储日志:EagleEye的埋点日志会输出到统一的日志目录下,与业务无关的RPC日志会写文件eagleeye.log,业务埋点日志会写文件biz-eagleeye.log。随后,tlog服务器会通过每台应用机器上的哈勃agent实时的不断抓取EagleEye日志,按照日志类型不同,将会有不同的处理方式:
1. 全量原始日志都会直接存到HDFS中;
2. 带有实时标记的EagleEye原始日志,放入HBase中存储;
3. 业务日志有一部分会被直接处理,存储到HBase,有一部分会作为消息发送出去,由感兴趣的业务系统订阅处理;
4. 全量日志存储到HDFS之后,EagleEye服务器会每小时创建MapReduce任务,在云梯上完成对全量日志的调用链合并、分析
以及统计,合并好的调用链依旧保存在HDFS,分析和统计的结果也输出到HDFS,EagleEye服务器在任务执行完毕之后,会将
结果拉取到本地建立索引,并保存到HBase。在保存完毕后,就可以在EagleEye的数据展现页面看到分析结果了。
5. 另外,EagleEye 还做了调用的实时统计,有分钟级别的实时链路大盘、应用大盘、服务大盘和入口大盘,帮助快速定位故障问题所在。

收集和存储日志

  • EagleEye的实现之分析调用链:基于入口对调用链做链路分析;

分析调用链

微博Watchman

SEE: http://www.infoq.com/cn/articles/weibo-watchman

  • Watchman系统架构

Watchman系统架构图

系统组成

  • watchman-runtime组件利用字节码增强的方式在载入期织入增强逻辑(load-time weaving),为了跨进程/线程传递请求上下文,对于跨线程watchman-enhance组件通过javaagent的方式在应用启动并载入class时修改了JDK自身的几种线程池(ThreadPool或几类Executor)实现,在客户代码提交(execute或submit)时对传入的runnable/callable对象包上具有追踪能力的实现(proxy-pattern),并且从父线程上去继承或初始化请求上下文(request-context);
  • 而对于跨进程的RPC场景,则动态增强传输层的客户端和服务端的逻辑。微博平台使用的Motan RPC框架有着类似filter-chain的流程,watchman-aspect会插入自己的filter实现;实现的逻辑就是在RPC请求前获取请求方的请求上下文,序列化后装配近请求体中,服务方获取请求后,再从请求体中反序列化请求上下文,同时设置到线程上下文中(ThreadLocal)。
  • 普通Java调用的处理方式(埋点/追踪)则是通过AspectJ的静态织入,相信广大读者对AspectJ都不陌生,它提供非常强大的AOP的能力,我们使用AspectJ来定义几类切面,分别针对WeiboAuth、HTTP接口、资源客户端的下行方法等。再利用AspectJ的语法定义各个切点,形如:
@AfterReturning(value="execution(public $type $signature($param))",returning="$return")
  • watchman-core组件内置几类策略,分别用来控制收集数据的范围、收集数据的采样率、以及几种控制策略。
  • watchman-aspect组件通过异步日志(async-logger)会在各个节点上输出日志文件,如何将这些分散的日志源源不断的收集汇总并计算?通过watchman-prism组件(基于Scribe),将日志推送到watchman-stream组件(基于Storm),利用这两个业界成熟的系统以流式的方式处理数据,stream中bolt会根据需求进行聚合、统计等计算(针对性能数据),规范化、排序(针对调用链数据),之后写入HBase中。这个过程通过benchmark反映出的结果来看,完全能达到准实时的要求(30s左右)。
  • 服务质量保障是Watchman系统的另一特点,在面向服务的架构(SOA)中,各个服务的提供方需要给出SLA(service level agreement)数据,量化服务的各种指标(如吞吐、承载)和服务质量(如99.99% <50ms)。运行时watchman-stream将不断计算得出的性能数据与通过watchman-registry获得的各服务的SLA数据进行比对。结果会反映到Dashboard上,这里与运维的告警系统等集成,可以及时将状况推送出去。

京东CallGraph

  • 产生背景:SOA化和微服务;基于Google发表的分布式日志跟踪论文;相似的有淘宝鹰眼和新浪WatchMan;
  • 核心概念:调用链包含了从源头请求到最后底层系统的所有环节,中间通过全局唯一的TraceID透传;
  • 特性及使用场景:方法调用关系(单次调用的问题排查)、应用依赖关系(容量规划、调用来源、依赖度量、调用耗时、调用并行度、调用路由)、与业务数据集成(将公司业务与第三方业务进行关联);
  • 设计目标:低侵入性、低性能影响、灵活的应用策略、时效性;
  • 实现架构:核心包(被各中间件引用,完成具体的埋点逻辑,日志存放在内存磁盘上由Agent收集发送到JMQ)、JMQ(充当日志数据管道)、Storm(对数据日志并行整理和计算)、存储(实时数据存储有JimDB/HBase/ES,离线数据存储包括HDFS和Spark)、CallGraph-UI(用户交互界面)、UCC(存放配置信息并同步到各服务器)、管理元数据(存放链路签名与应用映射关系等);

CallGraph实现架构

  • 埋点和调用上下文透传:前端利用Web容器的Filter机制调用startTrace开启跟踪,调用endTrace结束跟踪;各中间件调用clientSend、serverRecv、serverSend和clientRecv等API;对于进程间的上下文透传,调用上下文放在ThreadLocal;对于异步调用,通过Java字节码增强方式织入,以透明的方式完成线程间上下文的透传。

CallGraph透传

  • 日志格式设计:固定部分(TraceID、RpcID、开始时间、调用类型、对端IP、调用耗时、调用结果等)、可变部分;
  • 高性能的链路日志输出:开辟专门的内存区域并虚拟成磁盘设备,产生的日志存放在这样的内存设备,完全不占用磁盘IO;专门的日志模块,输出采用批量、异步方式写入,并在日志量过大时采取丢弃日志;
  • TP日志和链路日志分离:链路日志通常开启采样率机制,比如1000次调用只收集1次;但是对于TP指标来说,必须每次记录,因此这两种数据是独立处理互不影响;
  • 实时配置:通过CallGraph-UI和UCC实时配置,支持基于应用、应用分组、服务器IP多维度配置;
  • 秒级监控:针对业务对实时分析的需求,采用JimDB存放实时数据,针对来源分析、入口分析、链路分析等可以提供1小时内的实时分析结果;
  • 未来之路:延迟更低、完善错误发现和报警、借助深度学习挖掘价值。

美团MTrace

SEE:https://www.slideshare.net/meituan/08-63406714
SEE:http://tech.meituan.com/mt-mtrace.html

  • MTrace,美团点评内部的分布式会话跟踪系统,其核心理念就是调用链:通过一个全局的ID将分布在各个服务节点上的同一次请求串联起来,还原原有的调用关系、追踪系统问题、分析调用数据、统计系统指标。这套系统借鉴了2010年Google发表的一篇论文《dapper》,并参考了Twitter的Zipkin以及阿里的Eagle Eye的实现。
  • 使用场景:网络优化(通过IP查询一次分布式请求是否有跨机房调用);瓶颈查询(通过可视化了解整个系统调用的耗时,优化整个系统的效率);优化链路(一个服务对相同接口多次请求进行优化,比如改成批量接口或者提高整个系统调用的并行度);异常log绑定(可以将请求的参数、异常log等信息通过traceId进行绑定,很容易地就把这些信息聚合到了一起,方便业务端查询问题);透明传输数据(put和putOnce);
  • 系统架构:提供统一的SDK,在各个中间件中埋点,生成traceID等核心数据,上报服务的调用数据信息。尽量在各个统一的中间件中进行显式埋点,虽然会导致代码间耦合度增加,但是方便后续定位问题。Agent仅仅会转发数据,由Agent判断将数据转发到哪里,这样就可以通过Agent做数据路由、流量控制等操作。数据埋点的四个阶段:Client Send、Server Recieve、Server Send和Client Recieve;上下文归档,会把上下文数据异步上传到后端,为了减轻对业务端的影响,上下文上报采用的是异步队列的方式,数据不会落地,直接通过网络形式传递到后端服务,在传递之前会对数据做一层压缩,主要是压缩比很可观,可以达到10倍以上,所以就算牺牲一点CPU资源也是值得的。在SDK与后端服务之间加了一层Kafka,这样做既可以实现两边工程的解耦,又可以实现数据的延迟消费。调用链路数据的实时查询主要是通过Hbase,使用traceID作为RowKey,能天然的把一整条调用链聚合在一起,提高查询效率。离线数据主要是使用Hive,可以通过SQL进行一些结构化数据的定制分析。前端展示,主要遇到的问题是NTP同步的问题,因为调用链的数据是从不同机器上收集上来的,那么聚合展示的时候就会有NTP时间戳不同步的问题。

MTrace系统架构

总结

  1. 分布式调用链跟踪的核心基本都是Google Dapper论文所述,使用全局TraceID表示一条调用链,连接各个服务调用(用SPAN表示),通过分析SPAN之间的父子关系形成跟踪树。另外通过中间件的埋点和业务自定义的Annotation,记录日志并采用收集器进行离线和在线分析,从而实现调用链跟踪、优化决策等信息。
  2. 实现的核心在于埋点、日志收集、调用链分析,难点在于调用链分析,如何实现可视化以及和告警系统等对接是判断是否好用的核心标准。
  3. 另外,为了最低限度降低对业务的影响,采样率、开关控制和日志优化手段也很有必要。
Gino Zhang wechat
扫一扫,关注我的微信公众号