引言
一个复杂的分布式web系统,前端的一次用户操作,对应的是后端几十甚至上百个应用和服务的调用,这些调用有串行的、并行的,那么如何确定前端的一次操作背后调用了哪些应用、服务、接口,这些调用的先后顺序又是怎样,业务链路监控系统就是用来解决这个痛点的。
实现原理
从流量入口(通常是前端的一次Http调用)开始,传递Trace(TraceId,RpcId,UserData),在整个业务链路上传递Trace信息,从前端、服务层到数据层一层一层传递下去,这样根据TraceId就可以识别具体调用属于哪条链路
Trace信息如何在链路内透传
Trace信息相当于在业务链路中的埋点信息
如下图:链路的调用分2种,系统内部的调用通常是线程内的调用,而经过RPC、HTTP、异步消息调用都是不同系统(不同线程间)的调用
2种场景的Trace信息透传:
线程/进程间传递使用参数传递:客户端调用服务端、异步消息调用属于信息从一个应用的线程转移到另外一个应用的线程,在2个线程之间传递Trace信息使用参数传递ThreadLocal 是什么?
上面讲了业务链路监控系统是如何实现无侵入式的Trace信息透传,那么ThreadLocal是什么,为什么可以实现线程内的数据传递。
首先,ThreadLocal是一个老家伙,它在jdk1.2的时候就已经存在了,首先看下ThreadLocal的注释:
ThreadLocal变量特殊的地方在于:对变量值的任何操作实际都是对这个变量在线程中的一份copy进行操作,不会影响另外一个线程中同一个ThreadLocal变量的值。
例如定义一个ThreadLocal变量,值类型为Integer:
ThreadLocal提供的几个主要接口:
范例代码:
执行结果:
结果分析:
thread0和thread1中对ThreadLocal变量seq的操作并没有相互影响。主线程在thread1启动前修改seq值对thread1无影响,thread1中seq初始值仍然是0。三个线程中调用get方法获取到的是不同的TestBean对象ThreadLocal变量线程独立的原理:
直接看ThreadLocal变量的赋值:
线程对象的threadLocals属性定义如下:
getMap返回的是线程对象t的threadLocals属性,一个ThreadLocalMap对象
createMap(t,value)的作用是初始化线程对象t的属性threadLocals的值:一个线程中调用ThreadLocal变量的get/set方法获取和修改的是当前线程中存储的value,当前线程无法修改另外一个线程的存储的value,这就是ThreadLocal变量线程独立的原因。
但是如果不同线程的value通过调用set方法指向同一个对象,ThreadLocal就丧失了线程独立性,范例代码:
和前面代码的区别在于,线程运行前,调用set方法将value置为外部的testBean变量,看运行结果:
所以ThreadLocal线程独立的前提是:不要使用set方法设置value为同一个对象,ThreadLocal对象会自动在线程第一次调用get方法中调用initialValue()方法生成一个类型的实例作为value。
ThreadLocal变量的特点是:线程独立,生命周期和线程的生命周期一致。正是这2个特点,决定了它可以在分布式的业务链路监控系统中用于Trace信息的传输。