Google Dapper和Alibaba EagleEye都是用于大規(guī)模分布式系統(tǒng)的業(yè)務(wù)鏈路監(jiān)控锚烦,2者都是基于ThreadLocal來透?jìng)鱐race信息掸犬,那么ThreadLocal是什么,為什么會(huì)被用來傳遞Trace信息通殃?
什么是業(yè)務(wù)鏈路監(jiān)控系統(tǒng):
一個(gè)復(fù)雜的分布式web系統(tǒng)度液,前端的一次用戶操作,對(duì)應(yīng)的是后端幾十甚至上百個(gè)應(yīng)用和服務(wù)的調(diào)用邓了,這些調(diào)用有串行的恨诱、并行的,那么如何確定前端的一次操作背后調(diào)用了哪些應(yīng)用骗炉、服務(wù)照宝、接口,這些調(diào)用的先后順序又是怎樣句葵,業(yè)務(wù)鏈路監(jiān)控系統(tǒng)就是用來解決這個(gè)痛點(diǎn)的厕鹃。
實(shí)現(xiàn)原理:
從流量入口(通常是前端的一次Http調(diào)用)開始,傳遞Trace(TraceId乍丈,RpcId剂碴,UserData),在整個(gè)業(yè)務(wù)鏈路上傳遞Trace信息轻专,從前端忆矛、服務(wù)層到數(shù)據(jù)層一層一層傳遞下去,這樣根據(jù)TraceId就可以識(shí)別具體調(diào)用屬于哪條鏈路
Trace信息是如何在鏈路內(nèi)透?jìng)鳎?/h4>
Trace信息相當(dāng)于在業(yè)務(wù)鏈路中的埋點(diǎn)信息
如下圖:鏈路的調(diào)用分2種请垛,系統(tǒng)內(nèi)部的調(diào)用通常是線程內(nèi)的調(diào)用催训,而經(jīng)過RPC、HTTP宗收、異步消息調(diào)用都是不同系統(tǒng)(不同線程間)的調(diào)用
2種場(chǎng)景的Trace信息透?jìng)鳎?/p>
線程/進(jìn)程間傳遞使用參數(shù)傳遞:客戶端調(diào)用服務(wù)端漫拭、異步消息調(diào)用屬于信息從一個(gè)應(yīng)用的線程轉(zhuǎn)移到另外一個(gè)應(yīng)用的線程,在2個(gè)線程之間傳遞Trace信息使用參數(shù)傳遞
線程內(nèi)傳遞使用ThreadLocal:線程內(nèi)部的方法之間調(diào)用混稽,無論調(diào)用了多少個(gè)方法采驻,都是一個(gè)線程內(nèi)部的調(diào)用,這些方法間傳遞Trace信息使用ThreadLocal
-
線程間透?jìng)?/p>
- HTTP:通過Http head或者body傳遞Trace信息匈勋。
- RPC:通過自定義的rpc協(xié)議(根據(jù)rpc框架實(shí)現(xiàn)的不同礼旅,各個(gè)公司有不同的rpc協(xié)議實(shí)現(xiàn))傳遞Trace信息。
- MQ:通過消息頭或者消息體攜帶Trace信息實(shí)現(xiàn)Trace信息從消息的生產(chǎn)者向消費(fèi)者傳遞颓影。
-
線程內(nèi)透?jìng)鳎?/p>
- ThreadLocal:進(jìn)入線程時(shí)各淀,將Trace信息存儲(chǔ)在ThreadLocal變量中,出線程時(shí)诡挂,從ThreadLocal變量中取出Trace信息碎浇,作為參數(shù)傳遞到下一個(gè)線程(應(yīng)用系統(tǒng))临谱。
ThreadLocal是什么?
上面講了業(yè)務(wù)鏈路監(jiān)控系統(tǒng)是如何實(shí)現(xiàn)無侵入式的Trace信息透?jìng)髋В敲碩hreadLocal是什么悉默,為什么可以實(shí)現(xiàn)線程內(nèi)的數(shù)據(jù)傳遞。
首先苟穆,ThreadLocal是一個(gè)老家伙抄课,它在jdk1.2的時(shí)候就已經(jīng)存在了,首先看下ThreadLocal的注釋:
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
* <pre>
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID, assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
* </pre>
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
}
ThreadLocal變量特殊的地方在于:對(duì)變量值的任何操作實(shí)際都是對(duì)這個(gè)變量在線程中的一份copy進(jìn)行操作雳旅,不會(huì)影響另外一個(gè)線程中同一個(gè)ThreadLocal變量的值跟磨。
例如定義一個(gè)ThreadLocal變量,值類型為Integer:
ThreadLocal<Integer> tLocal = new ThreadLocal<Integer>();
ThreadLocal提供的幾個(gè)主要接口:
- set(T value):設(shè)置ThreadLocal變量在當(dāng)前線程中copy的值攒盈。
- get():獲取當(dāng)前ThreadLocal變量在當(dāng)前線程中copy的值抵拘。
- remove():移除當(dāng)前ThreadLocal變量在當(dāng)前線程中copy的值。
- initialValue():初始化ThreadLocal變量在當(dāng)前線程中copy的值
范例代碼:
package com.wow.testcode.threadlocal;
/**
* Created by arthur.hw on 2017/8/17.
*/
public class TestThreadLocal {
private ThreadLocal<TestBean> seq = new ThreadLocal<TestBean>() {
@Override
protected TestBean initialValue() {
return new TestBean();
}
};
public TestBean addSeq() {
System.out.println("thread --> " + Thread.currentThread().getName() + " --> " + seq.get());
seq.get().setId(seq.get().getId() + 1);
return seq.get();
}
public void setSeq(TestBean testbean) {
this.seq.set(testbean);
}
public TestBean getSeq() {
return this.seq.get();
}
public static void main(String[] args) {
TestThreadLocal tlocal = new TestThreadLocal();
TestBean testbean = new TestBean();
tlocal.setSeq(testbean);
tlocal.addSeq();
Thread thread1 = new Thread(new NewTestThread(tlocal, testbean));
Thread thread2 = new Thread(new NewTestThread(tlocal, testbean));
thread1.start();
thread2.start();
}
}
package com.wow.testcode.threadlocal;
/**
* Created by arthur.hw on 2017/8/17.
*/
public class NewTestThread implements Runnable {
private TestThreadLocal seq;
private TestBean testBean;
public NewTestThread(TestThreadLocal seq, TestBean testBean) {
this.seq = seq;
this.testBean = testBean;
}
@Override
public void run() {
System.out.println("thread inner--> " + Thread.currentThread().getName() + " --> " + this.seq.getSeq());
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread --> " + Thread.currentThread().getName() + " --> seq = "
+ seq.addSeq().getId());
}
}
}
package com.wow.testcode.threadlocal;
/**
* Created by arthur.hw on 2017/8/17.
*/
public class TestBean {
private Integer id = 0;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
執(zhí)行結(jié)果:
結(jié)果分析:
- thread0和thread1中對(duì)ThreadLocal變量seq的操作并沒有相互影響型豁。
- 主線程在thread1啟動(dòng)前修改seq值對(duì)thread1無影響僵蛛,thread1中seq初始值仍然是0。
- 三個(gè)線程中調(diào)用get方法獲取到的是不同的TestBean對(duì)象
ThreadLocal變量線程獨(dú)立的原理:
直接看ThreadLocal變量的賦值:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- getMap的作用是返回線程對(duì)象t的threadLocals屬性的值
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
線程對(duì)象的threadLocals屬性定義如下:
ThreadLocal.ThreadLocalMap threadLocals = null;
getMap返回的是線程對(duì)象t的threadLocals屬性迎变,一個(gè)ThreadLocalMap對(duì)象
- createMap(t,value)的作用是初始化線程對(duì)象t的屬性threadLocals的值:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 綜上看充尉,ThreadLocal.set(T value)的邏輯是:
首先獲取當(dāng)前線程對(duì)象t,然后調(diào)用getMap(t)獲取t.threadLocals衣形,如果獲取到的t.threadLocals為空驼侠,就調(diào)用createMap(t,value)對(duì)t.threadLocals進(jìn)行初始化賦值,否則調(diào)用map.set(this,value)覆蓋t.threadLocals的值谆吴。
一個(gè)線程中調(diào)用ThreadLocal變量的get/set方法獲取和修改的是當(dāng)前線程中存儲(chǔ)的value泪电,當(dāng)前線程無法修改另外一個(gè)線程的存儲(chǔ)的value,這就是ThreadLocal變量線程獨(dú)立的原因纪铺。
但是如果不同線程的value通過調(diào)用set方法指向同一個(gè)對(duì)象,ThreadLocal就喪失了線程獨(dú)立性碟渺,范例代碼:
package com.wow.testcode.threadlocal;
/**
* Created by arthur.hw on 2017/8/17.
*/
public class TestThreadLocal {
private ThreadLocal<TestBean> seq = new ThreadLocal<TestBean>();
public TestBean addSeq() {
System.out.println("thread --> " + Thread.currentThread().getName() + " --> " + seq.get());
seq.get().setId(seq.get().getId() + 1);
return seq.get();
}
public void setSeq(TestBean testbean) {
this.seq.set(testbean);
}
public TestBean getSeq(){
return this.seq.get();
}
public static void main(String[] args) {
TestThreadLocal tlocal = new TestThreadLocal();
TestBean testbean = new TestBean();
tlocal.setSeq(testbean);
tlocal.addSeq();
Thread thread1 = new Thread(new NewTestThread(tlocal, testbean));
Thread thread2 = new Thread(new NewTestThread(tlocal, testbean));
thread1.start();
thread2.start();
}
}
package com.wow.testcode.threadlocal;
/**
* Created by arthur.hw on 2017/8/17.
*/
public class NewTestThread implements Runnable {
private TestThreadLocal seq;
private TestBean testBean;
public NewTestThread(TestThreadLocal seq, TestBean testBean) {
this.seq = seq;
this.testBean = testBean;
}
@Override
public void run() {
// 線程運(yùn)行前鲜锚,將ThreadLocal變量的值賦值為外部的testBean對(duì)象
this.seq.setSeq(testBean);
System.out.println("thread inner--> " + Thread.currentThread().getName() + " --> " + this.seq.getSeq());
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread --> " + Thread.currentThread().getName() + " --> seq = "
+ seq.addSeq().getId());
}
}
}
package com.wow.testcode.threadlocal;
/**
* Created by arthur.hw on 2017/8/17.
*/
public class TestBean {
private Integer id = 0;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
和前面代碼的區(qū)別在于,線程運(yùn)行前苫拍,調(diào)用set方法將value置為外部的testBean變量芜繁,看運(yùn)行結(jié)果:
所以ThreadLocal線程獨(dú)立的前提是:不要使用set方法設(shè)置value為同一個(gè)對(duì)象,ThreadLocal對(duì)象會(huì)自動(dòng)在線程第一次調(diào)用get方法中調(diào)用initialValue()方法生成一個(gè)類型的實(shí)例作為value绒极。