为什么需要Trace
在现代IT系统,尤其是云原生、微服务系统中,一次外部请求往往需要内部多个服务、多个中间件、多台机器的相互调用才能完成。在这一系列的调用中,任意阶段出现的问题都可能导致外部服务失败或延迟升高,最终影响用户体验。在这种情况下,若想要精确分析并定位具体服务、模块、机器,你需要知道服务调用的关系是怎样的,错误是怎么传播过来的,具体是哪块的延迟比较大...这些Trace全部都可以告诉你。
Trace是什么
分布式链路追踪(Distributed Tracing,简称Trace)又名全链路数据追踪,为业务系统提供了整个服务调用链路的调用关系、延迟、结果等信息。相比访问日志这种记录顺时请求结果的数据,Trace则把内部的各种请求连接起来,组成一个服务调用的关系图,让你能轻松知道服务是怎么发起的、怎么流转处理的。
冰山一角:一个典型的Trace表示
上述是一个非常简单的程序,用户访问网站的时候自动给他们展示用户所在地的天气情况,这就需要调用两个外部的服务。整个交互过程如上午的1、2所示。:首先获取远端的IP地址,调用IP服务获取IP对应的地域信息;获取到地域信息后,调用天气服务查询对应地点的天气,最后返回给用户。
这个典型的调用也可以理解为一个Transaction,也就是端到端的请求->返回的过程,对于用户来说,就是一个简单的页面/接口请求,内部经历的每个请求->返回的记录就是Trace。图3是一个典型的Trace调用展示,从这个图中,可以看到请求整体的耗时、调用的接口、每个接口的耗时等信息。通过这种图形化的方式,就可以很容易的找到是哪个请求耗费了太多的时间,或者那个请求出现了错误。而图4是通过Trace信息还原出的DAG图,能够反映出系统中服务的依赖路径,能够更加清晰的了解系统。
上述就是一个最简单的Trace表示,也是Trace最开始的状态,能够表示一个请求具体的调用路径以及调用的详细信息,具备一定的可视化能力来辅助查看。当然Trace的能力还不仅仅于此,下面我们会详细展开真正友好的Trace方案所需具备的各项能力。
1. Trace标准化-OpenTelemetry
而无论图3还是4都是依赖Trace的“父子关系”来构造出来,而这个关系主要有Trace的组成Span而来(Trace Span的概念最早来自于Google的Dapper)。
- Trace记录了整个请求的生命周期,本身由一组Span组成,Span代表其中的一条调用链
- Span具有“父子关系”,这个父子关系由SpanID和ParentSpanID组成
- 当调用传播到下一层时,原来的SpanID就变成了ParentSpanID,随后会生成一个新的SpanID
- Span的传播可能会跨进程、跨主机,因此需要有一个传递TraceID、SpanID的途径,这个途径叫做Trace Propagation,Trace Propagation需要保证上下游的服务都能够支持一样的协议才行,否则传播到下一层时,因为服务无法识别,Trace会断掉。
- 由于系统中可能具有多个服务,还有队列、数据库、ServiceMesh等中间件,因此Trace Propagation需要遵循某个国际化标准,这个标准需要尽可能的通用。
- 最早在出现的国际化标准是OpenTracing,随后还有Google发起的OpenCensus项目
- 而目前OpenTracing项目和OpenCensus项目已经合并成为OpenTelemetry,OpenTelemetry已经成为Trace领域的唯一国际化标准。
而OpenTelemetry标准带来的好处不仅仅是解决各个系统之间的Trace互通问题,还有统一的SDK、自动化埋点方案、数据采集、Traces/Metrics/Logs互通等等好处。感兴趣的同学可以异步:OpenTelemetry介绍。
2. 埋点:手动 or 自动
对于开发者来讲,使用Trace最大的工作量是接入数据,我们需要在请求发起的时候创建一个Span,并且这个Span需要继承上一个Span传过来的TraceID、ParentSpanID等信息。下述代码就是一个简单的手动构造Trace的例子:
/* api-service */ app.use('/api/v1/whereami', async (req, res, next) => { const parentSpan = createContinuationSpan(tracer, req, 'whereami-request') const childSpan = tracer.startSpan('city-from-ip', {childOf: parentSpan}) const _ = await axios.get(`http://ip-api.com/json/${req.ip}`) childSpan.finish() // Do more work and create more children spans. parentSpan.finish() }) function createContinuationSpan(tracer, req, spanName) { const incomingSpanContext = tracer.extract(opentelemetry.FORMAT_HTTP_HEADERS, req.headers) if (incomingSpanContext == null) { return tracer.startSpan(spanName) } return tracer.startSpan(spanName, {childOf: incomingSpanContext}) }
对于一个庞大的系统而言,业务中涉及的请求非常多,每个请求都手动埋点的工作量太大,一般这种方式也很难在公司进行推广,因此各类语言的自动埋点的方式开始蓬勃发展。自动埋点在Trace领域又叫做Trace Instrumentation,主要利用一些代理、装饰器、代码注入、AOP等技术,在请求处理之前产生Span,在请求处理完成后保存Span并自动发送到Trace服务端。
下述是一个常见的服务架构,Service A和Service B都是HTTP的服务实现,网络通信使用ServiceMesh进行流量管理,HTTP请求都会经过HTTP框架进行处理,然后提交到Dispatcher模块,该模块会根据不同的URL转发到对应的Hander进行处理。这一架构中,可以自动埋点的地方有3个:
- ServiceMesh层,所有的HTTP请求在处理的时候都进行埋点
- HTTP Framework,在HTTP请求接收、处理的过程中埋点
- Dispatcher,在转发的时候进行埋点
一般而言,自动埋点也可以在多层同时进行,这样可以获取到每层对于性能的消耗是多少,对整体架构的性能开销有个更准确的理解。在生产上绝大部分情况都是使用自动埋点,对于有些自动埋点不能覆盖的场景,会用手动埋点的方式进行补全。例如某些语言不支持自动埋点、某个请求内部处理逻辑复杂需要手动创建子Span分成多个阶段...
自动埋点 |
手动埋点 |
|
工作量 |
低 |
高 |
支持的场景 |
少 |
全面 |
Trace完整度 |
低 |
高 |
性能开销 |
较高 |
低 |
3. 基于Trace的监控
我们知道Trace由Span组成,Span表示了当前某个调用的结果以及“父子”关系,而当某个Service的某个调用积累一定数量的时候,我们就可以分析出这个Service、调用的一些关键指标:访问次数(QPS)、延迟、错误率等。而这些通常也代表着我们的服务是否正常,因此通常情况下Trace不仅仅用来去查看某个请求是否有问题,更多的时候会用来监控服务是否正常。
4. PXX延迟-业务真实体验
基于Trace的监控不是从聚合指标而来,而是从每个调用来去计算出相应的访问次数、延迟、错误率,从原始数据计算的一个优点是可以较为准确的统计出PXX延迟,而PXX延迟也是反应用户对于我们业务真实体验的一个重要指标。因为通常情况下,平均延迟只能说明服务的情况,而不代表所有的客户访问都是如此的延迟,如果有一小部分的客户访问延迟很高,整体的平均延迟不会增加很多,但是这部分客户对于产品的体验会下降很多。
因此我们会更加关心P90、P95、P99之类的延迟指标,也就是按延迟正序排列,位于90%、95%、99%分位数的延迟大小。这部分延迟如果比平均延迟大非常多,那说明我们的服务在某些场景下对部分用户的体验非常不好,而这种情况下就需要去看这部分高延迟的指标具体耗时在哪个阶段,并进行针对性解决。
5. 高级Dependency分析
Dependency作为Trace的核心功能,是分析系统架构的利器,通过大数据工程(一般使用流计算、批量计算)分析所有上报的Span信息,将其中公共的关系提取、聚合,然后保存成一张DAG图。通常Dependency都只是从服务、调用维度进行计算,主要是因为这种计算的资源消耗比较少,毕竟服务、调用的组合比较少。
而实际场景中,我们希望知道在某个时间段、某一个进程、某一组容器的依赖关系,这就需要针对Span的Resource信息进行提取计算,组合关系会提升1-2个数量级,而计算复杂度和资源消耗也会随之提升。但带来的好处非常明显,我们能够知道更加细粒度的依赖关系,也更容易找出问题所在的机器、进程。
同时,在Dependency分析中,不仅仅只是计算DAG图的关系,而且还可以计算每个父子关系(也就是DAG图的边)的统计信息,包括调用次数、错误率、延迟等。因此在查看Dependency的时候,还可以看到某个节点调用其他服务的统计信息,更加有利于问题排查。
6. Trace自定义过滤
通常为了更好的分析Trace数据,我们都会在Span中附加一定的Tag信息,如果更精确的描述,Tag分位两类,分别Resource和Attribute。在OpenTelemetry协议中,这两种数据虽然都是同一数据类型,但含义却有着很大的不同:
- Resource代表Span的宿主信息描述,例如进程名、主机名、IP地址、设备类型、版本号、服务名、DB类型、K8s Pod名、环境信息、云相关信息,相对都是静止的信息。
- Attribute标识Span产生时和调用相关的属性信息,例如HTTP的方法、返回码、InFlow、OutFlow、URL、DB请求的SQL、数据队列请求的Topic信息等。
通常我们都需要按照不同的Resource、Attribute来过滤出我们关心的数据,用来定位问题主要发生在哪些类型的调用中。当然除了对这些Tag信息支持过滤外,还需要对Trace的其他属性,例如延迟、状态码、Span附加的Event信息等支持过滤。
7. Trace自定义分析
如果把Trace的自定义过滤功能比作是千里眼,则Trace的自定义分析功能就是显微镜。Trace自定义过滤从茫茫的数据中发现感兴趣的一部分Trace数据,而自定义分析对这部分Trace数据进行详细的排查,找出其中有问题的那一部分。这里的分析主要利用统计学相关的技术,按照不同的维度组合去分析,而这些维度包括上述的任一Resource和Attribute信息,从中去发现异常的维度。
8. Trace自动化分析
上述的自定义分析主要是靠人工+统计学,这种方式在数据维度不多的情况下还可以胜任,但如果要分析的数据维度超过3个时,纯粹的手工分析已经很难去把每个组合都尝试一遍。例如某个支付类型的调用成功率出现了下降,这个调用有地域、设备版本号、运营商、会员类型、支付类型5个维度信息,手动来做的话需要按照5个不同的维度去一一尝试,找到其中某个有明显分界的维度,再保存这个维度的信息,继续向其他4个维度进行尝试,最坏的情况下需要5*4*3*2*1次的手动分析才能找到问题的组合。而这个还是在问题只是由一个组合影响,如果有多个组合综合产生的结果,手动分析会收到多个结果的影响而更加复杂。
因此这时候我们就需要用算力和算法去做自动化的分析,利用算力从海量的组合中去一一分析,结合算法进行有效的剪枝,最终快速的得到造成调用错误率提升、延迟升高的维度组合。
9. 可观察性数据关联
Trace可做的事情很多,但并不是万能的,Trace更多的是应用的关系数据以及从这些数据提取出的应用健康度(延迟、错误率、QPS等)数据。对于问题的根因分析还缺少一些关键的信息,包括与之关联的基础设施健康信息(例如Pod/主机核心指标、异常事件、内核报错等)以及应用的详细错误日志。这些通常情况下叫做Metrics、Logs数据。
为了能够让Trace更好的关联上述的其他数据,Trace中的Span必须包含对应的主机名、容器名、服务名等信息,也就是OpenTelemetry中的Resource信息。而且Trace方案需要提供快速跳转并查询对应数据的能力。
10. 自动根因定位
当我们能够将Trace关联到Logs和Metrics时,就能够“顺藤摸瓜”找到问题的根本原因,无论是某台机器硬件故障(机器内核日志定位)、资源饱和(指标数据定位)、应用错误(应用报错日志)...
这些问题的排查相比纯粹的Trace分析会更加复杂,需要与更多的系统交互,需要去查看更多的数据,因此所花费的时间也更久。而故障发生时,1分钟的损失也是难以接受的,因此我们需要更快的去发现这些问题,这时AIOps中的自动根因定位就派上了用场:利用Trace内部的“父子”关联关系以及Resource与Logs、Metrics的关联关系自动“顺藤摸瓜”去排查问题的根因所在,大大节省问题排查的时间,能够让问题的根因定位从小时/分钟级降低到秒级。
总结
随着云原生、微服务技术的普及,Trace在运维领域的重要性也越发的凸显,构造一个简单的Trace系统并不难,但如何构建一个能充分发挥Trace优势的系统难度很大。上述介绍的这些Trace的特性,在生产中都极其适用,有这些特性的加持,问题排查将变得更加简单,尤其是后面6-10的特性价值更高,实现难度也更大:
- Trace自定义过滤和自定义分析能够帮助我们更好的去定位Trace相关的问题,这个能力要求Trace系统具备足够的强的存储、索引和算力。
- 可观察性数据关联能够实现问题的根因定位,前提是需要所有的数据都能遵循一个统一的标准,而OpenTelemetry正是首选方案。
- 自动化的根因定位能够缩短问题的排查时间,减少故障带来的损失,核心是AIOps算法需要具备超强的算力和更好的检测效果。
目前SLS已经发布了第一个版本的Trace服务,支持OpenTelemetry格式的Trace接入,并提供了上述1-7功能,后续功能的支持也在紧锣密鼓的研发中,欢迎大家试用。
传送门:https://sls.console.aliyun.com/lognext/trace
视频介绍:https://www.loom.com/share/8f13f0af46164b52a5e78e5b455ec622
参考
- https://opentelemetry.io/
- https://developer.aliyun.com/article/766070
- https://aiopsworkshop.github.io/
- https://landscape.cncf.io/
- https://github.com/apache/skywalking
- https://www.instana.com/blog/observability-vs-monitoring/
- https://www.katacoda.com/lokoms/scenarios/log-workshop-4
- https://wiprodigital.com/2020/04/30/how-aiops-impacts-business-performance/
- https://docs.lightstep.com/docs/view-traces
- https://docs.lightstep.com/docs/investigate-a-latency-regression
- https://www.jaegertracing.io/
- https://medium.com/jaegertracing/using-jaeger-to-trace-an-apache-camel-application-2b8118efbb4d
- https://github.com/apache/camel/blob/master/components/camel-opentracing/src/main/docs/opentracing.adoc
- https://www.sumologic.com/blog/logs-metrics-overview/
- https://www.novatec-gmbh.de/en/blog/5-reasons-why-opentelemetry-will-boost-observability-and-monitoring/