40张图看懂分布式追踪系统原理及实践

源 / 文/ 码海
前言
- 分布式追踪系统原理及作用 
- SkyWalking的原理及架构设计 
- 我司在分布式调用链上的实践 
分布式追踪系统的原理及作用
- 接口的 RT 你怎么知道? 
- 是否有异常响应? 
- 主要慢在哪里? 
单体架构


微服务架构


- 排查问题难度大,周期长 
- 特定场景难复现 
- 系统性能瓶颈分析较难 
- 自动采取数据 
- 分析数据产生完整调用链:有了请求的完整调用链,问题有很大概率可复现 
- 数据可视化:每个组件的性能可视化,能帮助我们很好地定位系统的瓶颈,及时找出问题所在 

分布式调用链标准 - OpenTracing
 这样 OpenTracing 通过提供平台无关,厂商无关的 API,使得开发人员能够方便地添加追踪系统的实现。
这样 OpenTracing 通过提供平台无关,厂商无关的 API,使得开发人员能够方便地添加追踪系统的实现。
- Trace:一个完整请求链路 
- Span:一次调用过程(需要有开始时间和结束时间) 
- SpanContext:Trace 的全局上下文信息, 如里面有traceId 


- 全局 trace_id:这是显然的,这样才能把每一个子调用与最初的请求关联起来 
- span_id: 图中的 0,1,1.1,2,这样就能标识是哪一个调用 
- parent_span_id:比如 b 调用 d 的 span_id 是 1.1,那么它的 parent_span_id 即为 a 调用 b 的 span_id 即 1,这样才能把两个紧邻的调用关联起来。 


- 怎么自动采集 span 数据:自动采集,对业务代码无侵入 
- 如何跨进程传递 context 
- traceId 如何保证全局唯一 
- 请求量这么多采集会不会影响性能 
SkyWalking的原理及架构设计
怎么自动采集 span 数据

如何跨进程传递 context
 dubbo 中的 attachment 就相当于 header ,所以我们把 context 放在 attachment 中,这样就解决了 context 的传递问题。
dubbo 中的 attachment 就相当于 header ,所以我们把 context 放在 attachment 中,这样就解决了 context 的传递问题。 小提示:这里的传递 context 流程均是在 dubbo plugin 处理的,业务无感知,这个 plugin 是怎么实现的呢,下文会分析
小提示:这里的传递 context 流程均是在 dubbo plugin 处理的,业务无感知,这个 plugin 是怎么实现的呢,下文会分析traceId 如何保证全局唯一

图示: snowflake 算法生成的 id
 每生成一个 id,都会记录一下生成 id 的时间(lastTimestamp),如果发现当前时间比上一次生成 id 的时间(lastTimestamp)还小,那说明发生了时间回拨,此时会生成一个随机数来作为 traceId。这里可能就有同学要较真了,可能会觉得生成的这个随机数也会和已生成的全局 id 重复,是否再加一层校验会好点。
每生成一个 id,都会记录一下生成 id 的时间(lastTimestamp),如果发现当前时间比上一次生成 id 的时间(lastTimestamp)还小,那说明发生了时间回拨,此时会生成一个随机数来作为 traceId。这里可能就有同学要较真了,可能会觉得生成的这个随机数也会和已生成的全局 id 重复,是否再加一层校验会好点。请求量这么多,全部采集会不会影响性能?
 这样的采样频率其实足够我们分析组件的性能了,按 3 秒采样 3 次这样的频率来采样数据会有啥问题呢。理想情况下,每个服务调用都在同一个时间点(如下图示)这样的话每次都在同一时间点采样确实没问题
这样的采样频率其实足够我们分析组件的性能了,按 3 秒采样 3 次这样的频率来采样数据会有啥问题呢。理想情况下,每个服务调用都在同一个时间点(如下图示)这样的话每次都在同一时间点采样确实没问题 但在生产上,每次服务调用基本不可能都在同一时间点调用,因为期间有网络调用延时等,实际调用情况很可能是下图这样
但在生产上,每次服务调用基本不可能都在同一时间点调用,因为期间有网络调用延时等,实际调用情况很可能是下图这样
SkyWalking 的基础架构
 首先当然是节点数据的定时采样,采样后将数据定时上报,将其存储到 ES, MySQL 等持久化层,有了数据自然而然可根据数据做可视化分析。
首先当然是节点数据的定时采样,采样后将数据定时上报,将其存储到 ES, MySQL 等持久化层,有了数据自然而然可根据数据做可视化分析。
SkyWalking 的性能如何


- 对多语言的支持,组件丰富:目前其支持 Java, .Net Core, PHP, NodeJS, Golang, LUA 语言,组件上也支持dubbo, mysql 等常见组件,大部分能满足我们的需求。 
- 扩展性:对于不满足的插件,我们按照 SkyWalking 的规则手动写一个即可,新实现的插件对代码无入侵。 
我司在分布式调用链上的实践
SkyWalking 在我司的应用架构

我司对 SkyWalking 作了哪些改造和实践
- 预发环境由于调试需要强制采样 
- 实现更细粒度的采样? 
- 日志中嵌入traceId 
- 自研实现了 SkyWalking 插件 
预发环境由于调试需要强制采样

实现更细粒度的采样?
 我们知道这种方式默认是 3 秒采样前 3 次,其他请求都丢弃,这样的话有个问题,假设在这台机器上在 3 秒内有多个 dubbo,mysql,redis 调用,但在如果前三次都是 dubbo 调用的话,其他像 mysql, redis 等调用就采样不到了,所以我们对 skywalking 进行了改造,实现了分组采样,如下
我们知道这种方式默认是 3 秒采样前 3 次,其他请求都丢弃,这样的话有个问题,假设在这台机器上在 3 秒内有多个 dubbo,mysql,redis 调用,但在如果前三次都是 dubbo 调用的话,其他像 mysql, redis 等调用就采样不到了,所以我们对 skywalking 进行了改造,实现了分组采样,如下 就是说 3 秒内进行 3 次 redis, dubbo, mysql 等的采样,也就避免了此问题
就是说 3 秒内进行 3 次 redis, dubbo, mysql 等的采样,也就避免了此问题日志中如何嵌入traceId?

 首先 log4j 的插件要定义一个类,这个类要继承 LogEventPatternConverter 这个类,并且用标准 Plugin 将其自身声明为 Plugin,通过 @ConverterKeys 这个注解指定了要替换的占位符,然后在 format 方法里将其替换掉。这样在日志中就会出现我们想要的 TraceId ,如下
首先 log4j 的插件要定义一个类,这个类要继承 LogEventPatternConverter 这个类,并且用标准 Plugin 将其自身声明为 Plugin,通过 @ConverterKeys 这个注解指定了要替换的占位符,然后在 format 方法里将其替换掉。这样在日志中就会出现我们想要的 TraceId ,如下
我司自研了哪些 skywalking 插件

- 插件定义类: 指定插件的定义类,最终会根据这里的定义类打包生成 plugin 
- Instrumentation: 指定切面,切点,要对哪个类的哪个方法进行增强 
- Interceptor,指定步骤 2 中要在方法的前置,后置还是异常中写增强逻辑 
 而 MonitorFilter 可以拦截所有客户端发出请求或者服务端处理请求,所以我们可以对 MonitorFilter 作增强,在其调用 invoke 方法前,将全局 traceId  注入到其 Invocation 的 attachment 中,这样就可以确保在请求到达真正的业务逻辑前就已经存在全局 traceId。
而 MonitorFilter 可以拦截所有客户端发出请求或者服务端处理请求,所以我们可以对 MonitorFilter 作增强,在其调用 invoke 方法前,将全局 traceId  注入到其 Invocation 的 attachment 中,这样就可以确保在请求到达真正的业务逻辑前就已经存在全局 traceId。

// skywalking-plugin.def 文件 
dubbo=org.apache.skywalking.apm.plugin.asf.dubbo.DubboInstrumentation
总结
好文推荐

字节跳动小组长无意中得知整个部门的薪资!自己只有28K!手下人却拿35K!怎么办?

居然有人提出“国家何时整治程序员的高薪现象”?码农们都气吐血了

太尴尬!百度某程序员向领导请假去面试,却在面试一楼大厅和领导相遇,网友:缘分啊!回去一起对对面试题!
END


顶级程序员:topcoding
做最好的程序员社区:Java后端开发、Python、大数据、AI
一键三连「分享」、「点赞」和「在看」
评论
