前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货 | Qunar全链路跟踪及Debug

干货 | Qunar全链路跟踪及Debug

作者头像
携程技术
发布2018-03-16 12:10:28
2.5K0
发布2018-03-16 12:10:28
举报
文章被收录于专栏:携程技术携程技术

作者简介

王克礼,去哪儿平台事业部基础架构Java开发工程师,参与开发和维护去哪儿内部中间件,包括配置中心、消息队列、日志收集及链路跟踪系统QTracer等。

随着公司业务的发展,支持业务的程序也会逐步发展;随着业务的复杂化和流量的增加,一般都会通过拆分的方式来分解不同的业务,将流量分摊到更多的机器上,从而支撑更复杂的业务和更大的流量。

这种分布式的系统会带来很多好处,也自然带来了一些问题。分布式意味着需要通过网络来进行调用,比如RPC调用、HTTPAPI调用、消息队列等;同时,不只是内部开发的程序是分布式的,程序依赖的很多服务也是分布式的,比如数据库、缓存、HBase、ES等。大量的分布式导致服务间的调用关系越来越来越复杂,处于分布式系统中某个节点的程序无法方便的掌握全局结构。

为了方便掌握分布式系统的全局情况,出现了一种分布式追踪系统,它能够将请求所经过的各个系统的操作用一个唯一ID标识并记录下来,便于查看和分析系统全局结构。QTracer就是Qunar内部开发维护的一套分布式追踪系统。

一、QTracer简介

1、简介

QTracer是Qunar内部开发维护的一套分布式追踪系统;它会为每个请求生成一个全局唯一的TraceID,然后将TraceID不断传递给下游系统;同时,在每个系统中,它都会记录各个系统里的各项操作;最后,通过TraceID将各个系统里记录的操作整合起来,还原出一个请求在整个分布式系统中的详细执行流程。

2、功能介绍

下面简单介绍一些QTracer提供的功能,让读者能够快速了解QTracer的功能,直观的感受QTracer的作用。

2.1执行链路查询

链路查询是QTracer的基础功能之一,它能够将整个调用流程完整的展示出来。上图就是链路展示页面,从图中能看到请求所在机房、描述、类型、执行时间等信息。

链路查询能够起到很多作用:

1. 它能清晰展示整个请求链路,帮助使用者快速了解全局情况。

2. 能够了解请求经过了哪些服务、哪些机器、耗时情况、跨机房调用情况等。

3. 能够了解各个服务的执行情况,比如是否执行成功、是否进行了重试、失败是否对整个请求造成了影响。

4. 能够快速看出整个请求的耗时分布,快速了解请求的瓶颈。

2.2关联日志查询

除了查询执行链路,QTracer还提供了根据TraceID查询关联日志的功能。上图就是关联日志查询的效果,能够展示出请求经过的各个系统上对应的日志。当你不能从链路数据里看到足够的信息时,可以查看相关的日志,往往日志中记录了更详细的信息。

2.3 按条件搜索

前面说明的两个功能都是需要根据TraceID来查询,有时我们需要的却是查询TraceID的功能。上图就展示了QTracer提供的TraceID搜索功能,可以根据TraceID前缀、起始应用、时间段、关键字等来搜索出对应的TraceID。

搜索时的关键字就是业务在操作执行时记录到Trace数据里的;比如可以将订单号记录到Trace数据中,这样后面就可以根据订单号查询出对应的TraceID,从而还原订单的处理流程。

2.4 服务上下游关系

上面提到的功能都是直接查询、搜索原始的Trace数据,实际上QTracer通过对Trace链路的实时分析,提供了更多的功能。上图中的服务上下游关系就是一例,通过对Trace链路中的调用关系进行分析,可以得到服务间的依赖关系,也能得到服务调用的QPS、耗时等数据。

2.5数据库操作统计

除了对服务进行分析,QTracer还对数据库操作等进行了分析。上面的图就展示了表级、语句级的执行次数统计。实际上QTracer提供的数据库操作相关统计更多,一是提供了库、表、语句三个维度的QPS、耗时情况统计,方便了解执行情况;二是提供了最近最慢数据操作的统计,方面提前发现问题,比如某些索引没有正确建立。

2.6 透明数据传递

Trace链路记录的时候就要贯穿多个系统,它是否能够作为一个旁路来传递数据呢?QTracer据此提供了透明数据传递功能,利用Trace链路不断传递的特性将上游数据向下游不断传递,避免各个业务为了非业务参数而修改接口。

透明数据传递主要用来传递一些开关、标识,或者是一些非请求业务相关的数据。下面说几个例子:

1. ABTest时可以使用它传输分支标识,从而控制流程走向。

2. 单元化服务里利用它传递单元标识符,避免跨单元调用。

2.7 其它

当然,前面介绍的不是QTracer的所有功能,只是举了一些典型功能。除此之外,QTracer还提供了异常自动关联TraceID、服务调用QPS分析、最近最慢服务调用统计、操作失败情况统计等功能。

二、QTracer客户端核心设计

1、数据模型

上图展示了QTracer中一个Trace的基本结构,Trace由多个Span组成。

Trace是由多个Span聚合而成的树形结构,它表示一次完整的请求链路。

Span是Trace的基本组成单元,它表示请求涉及到的一个个单独记录的操作。Span中保存了服务描述、操作起始结束时间、操作结果、操作类型等信息。

2、基本概念

在Span中记录了许多不同作用的数据,它们都有不同的作用场景,下面笔者将逐个介绍Span中涉及到的这些基本概念。

2.1 TraceID

TraceID简单来说就是一个全局唯一ID,QTracer利用它来关联整个请求链路上的所有操作。可以看出,TraceID是一个需要在系统间不断传递的数据。

TraceID原则上来说只需要保证全局唯一即可,使用UUID这种也没有关系。不过QTracer里的TraceID做了一些设计,在TraceID里包含了更多的信息,比如起始应用、起始机器、生成时间、采样标识等,一方面TraceID更加规律,另一方面也方便调查Trace相关的问题。

2.2 SpanID

TraceID标识了整个调用链路,而SpanID则是标记了链路中的一个个操作。通过SpanID可以看出服务的执行顺序和调用关系。由于需要通过SpanID确定关系,所以SpanID也需要不断传递。

下面简单介绍下QTracer的SpanID设计方案:

1. 取SpanID为1的Span作为Root Span,表示一个请求的起点。

2. 标记同一级的顺序调用时,SpanID的本级不断增长,比如1.1、1.2、1.3这种。

3. 标记调用关系时,则需要增加SpanID的层级,比如1、1.1、1.1.1这样。

2.3 TimelineAnnotation

TimelineAnnotation用于记录一个Span内部的时序性信息。例如:

1. HTTP客户端的一次请求记为一个Span

2. 实际上一次请求会有多个步骤,比如建立连接、写入请求、读取回复等

3. TimelineAnnotation就是记录这种内部的时序性操作的

通过TimelineAnnotation可以看到各个阶段的具体起始时间,一个简单的应用场景就是当HTTP请求失败时,能通过它看到进行到哪个步骤失败了,方便快速定位问题。

2.4 KVAnnotation

KVAnnotation是最常用的一种,它用于记录业务自己关心的自定义数据,比如订单号、UID等。记录之后,查询Trace链路是能看到这些记录的数据,也能通过这些数据反向搜索出相关的TraceID。

2.5 TraceContext

TraceContext就是透明数据传递需要使用到的概念,它也是保存业务自定义数据的,但是TraceContext中的数据不会被收集,而是不断的随着链路向下游传递。

3、核心API

上图展示了QTracer的核心API使用方式,主要就是利用startTrace函数开启一个新的Span,然后利用各个add函数添加不同种类的数据。

实际上,用户基本不需要使用这些核心API,公司里的很多组件都默认添加了QTracer的埋点,直接使用默认埋点在配合一些快捷使用方式基本就足够了。

4、Trace的延续

为了关联多个系统的操作,必须要把上下文不断的传递下次,所以Trace的延续就是一个关键性的问题。

首先,我们先介绍一下单个系统内部如何延续Trace链路,它分为同步调用和异步及跨线程调用两种情况。

1. 同步调用。同步调用时,延续Trace链路非常简单,QTracer内部会利用ThreadLocal来保存上下文关系,每次开启新的Span时,直接从ThreadLocal里获取当前的TraceID和SpanId即可。

2. 异步调用及跨线程调用。这种情况下,ThreadLocal是无法生效的,只能显式的延续上下文关系。对于线程及线程池,QTracer提供了快速延续的包装方法,使用也非常方便;而异步调用则只得利用核心API进行延续。

其次,除了单个系统内部的传递,还有许多情况需要跨机器延续。分布式情况下跨网络请求时非常常见的,跨网络的情况和跨线程是非常相似的,都是需要手工进行延续。为了方便地跨网络传输上下文关系,QTracer在内部使用的Dubbo、HTTP、MQ等组件里都加入了自动传输上下文的功能,一般来说不需要使用方关注。

5、无侵入埋点

前面提到很少需要使用核心API,因为直接使用核心API记录对用户不够友好,太繁琐了。为了方便使用者,我们对各个组件都添加了无侵入的埋点,用户直接开启采样就能得到足够的信息,基本不需要自己记录过多的数据。

无侵入埋点主要包含两种情况:

1. 对于公司内部维护的组件,可以直接添加埋点代码,这样能够更精确的控制功能,记录更多信息。比如Dubbo、消息队列这种组件。

2. 对于不是公司内部维护的组件,由于无法修改源码添加埋点功能,所以采用了字节码修改的方式,能够在运行时为指定的类添加埋点功能。比如MySQL和PG的driver。

6、字节码插桩

字节码插桩功能在QTracer中使用非常广泛,这里简单介绍一下。字节码插桩就是一种运行时动态修改字节码的技术,能动态调整代码的行为。它和代理的作用很像,但是实现上完全不同。一般来说需要为JVM添加代理(agent)来启用字节码插桩功能。

QTracer实现了一套利用配置指定对某些类的方法进行插桩的功能。比如,针对MySQL和PG的操作的插桩就是通过在配置文件中指定驱动中的方法、字段等实现的。同时,当有有新的客户端需要插桩接入时,直接在配置中心添加新的插桩配置即可直接生效。

7、本地方法快速插桩

除了中间件、数据库driver等预先埋点的组件,有些业务系统还想要跟踪一些重要的本地方法。这种时候直接使用核心API ?核心API对业务来说使用起来比较麻烦,需要熟悉和API的使用,避免使用错误,同时也会加入很大业务无关代码。

为了解决这个问题,QTracer提供了一个方便的解决方案:注解。利用注解标记需要跟踪的方法和需要记录的数据。然后程序编译时自动生成一份本地的插桩配置,启动时QTracer载入这个本地配置即可自动对那些指定的方法进行插桩。

上图展示了几个注解的使用场景,@QTrace注解标记要跟踪的方法,@QP 标记要记录的参数,@QF 标记要记录的成员变量。

8、日志关联

QTracer提供了根据TraceID查询关联日志的功能,但是日志是如何关联到TraceID的呢?QTracer在实现时利用了MDC(Mapped Diagnostic Context)来保存TraceID和SpanID,MDC中的数据是可以直接输出到日志中的。

Span生成时将TraceID和SpanID保存到MDC中,等一个Span结束时将这两种数据清空;这样一来,在Span表示的操作期间,所有记录的日志都能够同时记录当前的TraceID和SpanID。使用方通过配置日志的log pattern,将QTracer的信息默认输出到日志中。后面等日志被实时日志收集系统收集上来的时候,利用实时任务分析日志即可获得TraceID到日志的关联关系。

三、QTracer系统架构

这是整个QTracer系统的简单架构图,包括数据记录与收集、数据处理分析、数据展现这三个部分。

1、Trace数据记录和收集

QTracer利用本地日志进行数据记录,将数据全部暂存到本地日志,然后利用实时日志收集将数据全部发送到专门的Kafka集群暂存。

数据记录时尤其重要的就是控制资源消耗,插桩时要尽量降低额外损耗,记录日志时也要小心对IO和磁盘空间的占用情况。为了尽量降低记录日志的损耗,QTracer内部实现了异步批量写日志;控制批量大小,避免占用过多内存;日志文件按照大小轮转而不是时间轮转,同时严格控制日志文件数量,这样能避免大量数据占据过多磁盘空间;同时在极端情况下,为了避免过多占用资源,QTracer会选择丢弃一定量的数据。

2、Trace数据处理与分析

我们内部选择了Samza框架作为分析Trace数据的实时任务框架,主要是因为:

1. Samza和Kafka集成程度高,配合良好

2. Samza使用非常简单,API简单直接,功能在Trace数据分析这个场景上足够使用

3. 提供了一个基于rocksdb的本地cache,提供了异常恢复功能,方便编写需要缓存很多数据做聚合的任务

上图展示了Trace数据处理的一个大致流程。数据处理主要分为两条线路,针对Span进行处理和针对Trace链路进行处理。

针对Span进行的处理主要有:

1. 直接保存到HBase中提供快速查询。Span保存到HBase时,直接使用TraceID构成rowkey,使用SpanID作为列名,这样能提供非常快速的查询。

2. 利用实时任务将Span数据聚合形成完整的Trace链路。

3. 分析最近最慢操作,比如HTTP请求、数据库操作等,这种数据能够根据单独的Span数据分析出来。

4. 统计操作的QPS、耗时等数据。

针对Trace进行的处理主要有:

1. 对完成的Trace链路数据进行精简,删除重复数据和无用数据,然后保存到ES中提供多维度搜索功能。适度的精简能降低数据量,不保存冗余的数据。

2. 分析统计调用链路上的上下游调用关系。通过对完整的链路进行拆解,能够得到链路涉及的各个服务的上下游关系。

3. 分析上下游调用关系的同时,也能得到服务调用的QPS、耗时情况等。

4. 通过大量调用关系的聚合,能够得到整体上各个服务之间的依赖关系。有依赖关系之后,查问题时更容易定位问题。

3、数据存储

QTracer存储方面主要使用了HBase和ES两种。它们各自都有不同的作用,下面简单介绍一下。

HBase首先是用于保存Trace链路数据,这点前面也提到过;实际保存时为了避免HBase region写入过热,对TraceID做了一些变形,将分布均匀的字段尽量放到rowkey前面。其次,HBase还保存了根据服务调用统计出来的分钟级QPS、调用时间等数据。

ES首先是用于保存精简的Trace链路,提供了按条件搜索功能。其次,还保存了服务的上下游调用关系、近期的整个服务依赖关系等。

四、QTracer Debug

1、简介

除了QTracer和各个组件提供的预先埋点以及手工提前埋点,有时会遇到想要直接获取代码运行到某个位置是调用栈的具体状态。QTracer Debug就提供了这样一种功能,它类似于IDE中提供的debug功能,通过在源码中设置断点,可以获取实际代码运行时断点处的所有变量、调用栈信息,而且,这不会暂停应用,同时额外损耗也非常小,可以直接在线上使用。

下面简单说下使用流程:

1. 在前端页面上选择项目,浏览代码,在代码行上标记断点

2. 选择应用的机器,启用断点

3. 访问能经过断点的URL,带上指定的参数

4. 等待数据收集完成即可自动展示所有数据

2、详细实现

2.1 概要

展示层面借助Gitlab的API实现代码浏览和按行设置断点功能。然后借助QTracer 的字节码插桩功能在指定位置加入断点代码,执行时进入断点就能记录调用栈状态。直接利用QTracer现有的数据处理路径进行数据的记录和收集。后面的实时任务会筛选出含有debug数据的Trace链路,并提取断点数据保存到HBase。最后在前端展示断点数据。

2.2 断点添加

断点添加是一个核心部分,它的主要流程是:

1. 分析应用所有的类,建立源文件+行号到类的映射关系。

2. 根据断点位置找到需要添加断点的类

3. 分析类,收集类中所有变量的作用域信息

4. 修改类的字节码,在指定位置插入收集所有作用域内变量等数据的代码

2.3 数据收集及记录

QTracer Debug的数据记录直接依赖QTracer自身的KVAnnotation功能,直接把数据存放到QTracer的Span中。数据收集直接通过QTracer的写本地日志+日志收集方式。实时任务对QTracer数据进行筛选,保存到HBase中。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 携程技术中心 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档