链路跟踪归根到底只是一种理念和策略,简单的说就是在2次关联调用之间传递特定透传信息的能力。从组件设计的角度说其实关心的是是下面的几个特性:java
典型的例子就是Java系的方案,总的来讲java是一种编译语言,可是得意于虚拟机和字节码的实现方式,Java其实是具备动态语言的特性的。git
这类实现的基本思路就是在利用java-agent拦截具体类加载过程,在特定的类加载过程加入自定义的代码来实现trace的能力。程序员
这类方案的主要缺点是的只能用于java,可是只要是java技术栈的实现就几乎能够无任何限制的接入,对java技术栈的公司来讲是很是有效。接入成本也很是低,只要在启动命令中指定参数就能够了,不管是部署脚本仍是构建镜像都很方便。剩下另外一个一个缺点就是改字节码自己仍是有必定风险的。不过整体来讲稳定性仍是有保障的。
skywalkinggithub
并非全部公司内部都是java的,其余语言并无改字节码这种骚操做,或者认为这种方式太过粗暴该怎么办呢?web
这种状况下基本的思路就是抽象出协议层面的概念,让各个组件的实现内部支持链路跟踪的实现,trace日志组件的信息聚集也由组件完成。若是有业务方有特殊须要接入链路跟踪系统也须要能够依照相同的约定与trace进行交互。此外还考虑须要和各种开源的组件相适配。redis
这种状况下,协议层面的设计就显得很重要,是须要各方都认同和理解的方案,协议自己的完备性就是很是重要的。就目前来讲最为著名的就是opentracing的规范,基本上能够视为链路跟踪领域的事实上的标准。数据库
从我的来看我这种方式是更优的策略,并且在大公司的内部,推行标准化的编程规范也是必要的,可是这也是双刃剑,trace的实现依赖于标准化的程度,由于链路这种东西只要中间断过一次就没法达到链路跟踪的效果了。apache
另外一个问题是即便标准化也是有限度的,好比跨线程的信息传递绝大多少公司内部的标准化就很难作。这样作出来的功能其实仍是不如字节码加强来的简单有效。
jaeger 、CAT、SOFATracer、zipkin、dapper编程
固然随着近几年容器化和service mesh的推动,基于servicemesh的方案也是能够作链路跟踪的。经过sidecare劫持流量,能够构建出不依赖具体语言或者rpc的链路跟踪系统,从模型上看确实是更为理想的模型,不过如何让运行时的程序内部也感知到链路跟踪也是一个问题,同时mesh的各类方案截止现阶段其实仍是处在探索和实践阶段并无完美的解决方案。
jaegerapi
现有的链路跟踪的模型大多参考了dapper的实现,opentracing的规范也对模型设计有很大的影响。opentracing的语义。下面大部份内容都摘取自这两部份内容
简单的说一次外部调用能够被多个内部请求组合完成,这一过程能够被描述成一个树的形式,而每次调用被定义为一个span。整个调用能够被称为一个trace,能够用一个惟一id标识。
span是构成调用树的最小单元。一般来讲包括下面几个部分,一般也会被一个惟一id标识:
Causal relationships between Spans in a single Trace [Span A] ←←←(the root span) | +------+------+ | | [Span B] [Span C] ←←←(Span C is a `ChildOf` Span A) | | [Span D] +---+-------+ | | [Span E] [Span F] >>> [Span G] >>> [Span H] ↑ ↑ (Span G `FollowsFrom` Span F)
在如上的链路跟踪调用树比较直观了解,只有2个地方须要解释下:
一个是span中的引用,其实我以为这应用更理解不一样,在实现上大部分不太可能找到全部的子span的引用,或者没有必要。大部分状况下其实使用parent-span-id的概念来构造。一个引用的类型,
其实这里我其实更想讨论的是如何界定span的范围,但以我我的的来看并不太同意将异步场景都串联起来。主要基于2点考虑
一是trace不少时候用于性能分析或者依赖分析之类的场景,在这2种比较典型的场景下其实将异步场景串联起来并无太大的意义,反而不利于后续的数据分析工做
另外一缘由是即便不使用spanid自己的结构串联也并不意味着丢失了关联信息,由于trace自己是有信息透传能力的,咱们彻底能够构造一个相似logid的概念或者业务上有意义的数据,进行透传即便链路自己再也不同一个trace下,可是信息依然是能够透传的。
透传的实质上就是在两个上下文之间完成spancontext的构造。总的来讲须要作2件事情,一件事情用尽量无侵入的方式传递spanContext用于重建span。另外一件就是在当前的context构造的spanwapper中构造一个新的span并推入栈顶,固然也能够根据状况来选择是否构造新的span。下图展现了一个跨线程传递的例子。
在透传的场景下其实参数是可选的,大部分场景下,trace只针对跨runtime的请求处理,内部跨线程不会建立信息的span,这种状况下不会构造新的span,若是是rpc调用则会生成新的span。
另外一个须要讨论的是SpanContext:SpanContext其实在不一样场景下都有不一样的概念。这里更多的指的是用于下游恢复链路的构造部分和须要透传的参数。
根据上述内容,很容易理解一个span实际上是须要对端构造的2条日志才能完整的构造。这2条日志会被日志被各自实例收集起来各自上传,仅仅有少许的参数会随rpc之类的介质透传给下游用于恢复链路或者参数透传,总的来讲参数透传成3个部分:
这里面描述的是通用的分布式链路追踪的模块设计类型,不一样的系统可能在不一样的地方有所取舍。但整体来讲遵循
链路跟踪有不少实现形式,从我我的的理解来看须要作2层抽象,一层是加强点层面的。
具体实现上代码模块仍是分了不少有意思的部分代码模块拆分红了多个部分
代码层面其实要作2层抽象,
logid是否是traceid?两种有什么区别?
严格来讲log-id不是trace-id,可是也能够是。trace本质上是提供透传信息的能力,logid经常使用于串联日志信息,因此大部分场景下logid都是trace的透传能力在系统间透传的,在系统内部每每是threadlocal或者context的概念保存。
初次以外,以前咱们还讨论过一种有意思的问题就是异步场景下的串联。根据以前的内容咱们其实讨论过,trace自己因为起止时间的限制虽然能够用于异步场景,可是这样会给信息分析带来不少麻烦,在实际中我其实更倾向于将traceid定义在同步调用的scope内,在异步场景下,好比异步rpc,或者消息队列场景下,从新构造logId。
这里还有一个问题没有解释就是如何实现字节码加强的,基本原理是用的java-agent。java虽然是编译性的语言可是因为jvm和classloader的存在,java具备必定的动态特性。java的实际运行逻辑其实是取决于jvm中的字节码,好比大多数javaer怀念的事务管理的注解,本质上上就给某些方法或者成员变量打上标记,运行过程当中生成一个代理类同时在原有方法的基础上添加一些事务管理的模板。不管是生成新的代理类或者改变原有的类的字节码,从而实现动态代理。
咱们回到trace的使用场景下,其实咱们也是但愿对字节码实现加强,理论上说也是能够基于自定义的类加载去制做动态代理实现的,可是一个主要的问题是没有办法控制全部的类加载器,其实trace但愿的是在某个方法上实现wapper而并不关心具体的类加载是哪一个。java自己提供了一种java-agent机制能够实现拦截全部的类加载过程,或者在运行过程当中重载某个类的后门,显然更适合咱们的场景。使用中只要是实现了对应接口,并打包成jar就能够。java-agent提供了2种方式一种是做为启动参数与jvm-runtime同时启动。或者在jvm实例启动以后,做为队列进程启动并attchment到jvm进程上。
这里不详细讨论代码实现的方式,由于网上例子不少,这里想说的实际上是一套经常使用的java-agent使用的设计方式,这样对理解其余开源设计也有不少帮助。
一个典型的java-agent相关的模块不少状况下包括上面几个部分,
java-agent应用其实很是广,这里能够举几个列子;
首先介绍下环境隔离的概念:大部分的开发模式都是基于giflow的。若是只有一套环境的话就会有多个代码合并的蛋疼问题,若是建多套环境的话成本有很高(好比独立的数据库,Nginx、注册中心、redis,以及大量的依赖服务),那有没有一种方式既能够建立建立多套环境又避免蛋疼的产品问题呢?
环境染色的方案就是trace在各个中间件请求下解析出入口环境信息,而且将其做为参数透传,各个中间件组件配合该信息将trace路由到对应集群上的方案:举一个简单的例子以下所示:
基本的思路就是这样,全部集群都有一套基准环境,一般部署master分支,而本次分支涉及到的变革部署一套feature集群。。全部的公共组件都用同一套,好比Nginx、注册中心、数据库、kafka集群等等。可是应用用到的资源会略有区别,好比说注册中心上带有集群的环境信息,rds能够建一个带有环境名后缀的影子表。kafka建有对应环境名后缀的topic。 用户请求的时候使用相同的域名可是附带上具备环境名的header,app在接受请求的时候会解析header并将入口环境信息放入baaage中,该信息会随链路下传。
对于rpc,客户端作路由的时候会根据环境信息优先选取特定子环境的集群,若是没有则调用基准环境,基准环境中的应用在调用的时候也能够根据相同的规则优先调用子环境。即便调用穿过了中间件好比队列,则传递的消息也附带有环境信息,trace也能够根据信息解析出路由规则并进行透传。不过为了不消息被基准环境的app消费仍是须要建特定子环境的topic。
微信公众号:神奇的程序员
接受一线大厂内推(微软、阿里、头条、网易),详情请联系公众号或者发邮箱Andrewzhch@gmail.com