業務鏈路監控(Google Dapper)和ThreadLocal

引言

一個複雜的分佈式web系統,前端的一次用戶操作,對應的是後端幾十甚至上百個應用和服務的調用,這些調用有串行的、並行的,那麼如何確定前端的一次操作背後調用了哪些應用、服務、接口,這些調用的先後順序又是怎樣,業務鏈路監控系統就是用來解決這個痛點的。

實現原理

從流量入口(通常是前端的一次Http調用)開始,傳遞Trace(TraceId,RpcId,UserData),在整個業務鏈路上傳遞Trace信息,從前端、服務層到數據層一層一層傳遞下去,這樣根據TraceId就可以識別具體調用屬於哪條鏈路

Trace信息如何在鏈路內透傳

Trace信息相當於在業務鏈路中的埋點信息

如下圖:鏈路的調用分2種,系統內部的調用通常是線程內的調用,而經過RPC、HTTP、異步消息調用都是不同系統(不同線程間)的調用

業務鏈路監控(Google Dapper)和ThreadLocal

2種場景的Trace信息透傳:

  • 線程/進程間傳遞使用參數傳遞:客戶端調用服務端、異步消息調用屬於信息從一個應用的線程轉移到另外一個應用的線程,在2個線程之間傳遞Trace信息使用參數傳遞
  • 線程內傳遞使用ThreadLocal:線程內部的方法之間調用,無論調用了多少個方法,都是一個線程內部的調用,這些方法間傳遞Trace信息使用ThreadLocal
  • 線程間透傳
  • HTTP:通過Http head或者body傳遞Trace信息。
  • RPC:通過自定義的rpc協議(根據rpc框架實現的不同,各個公司有不同的rpc協議實現)傳遞Trace信息。
  • MQ:通過消息頭或者消息體攜帶Trace信息實現Trace信息從消息的生產者向消費者傳遞。
  • 線程內透傳:
  • ThreadLocal:進入線程時,將Trace信息存儲在ThreadLocal變量中,出線程時,從ThreadLocal變量中取出Trace信息,作為參數傳遞到下一個線程(應用系統)。

ThreadLocal 是什麼?

上面講了業務鏈路監控系統是如何實現無侵入式的Trace信息透傳,那麼ThreadLocal是什麼,為什麼可以實現線程內的數據傳遞。

首先,ThreadLocal是一個老傢伙,它在jdk1.2的時候就已經存在了,首先看下ThreadLocal的註釋:

業務鏈路監控(Google Dapper)和ThreadLocal

ThreadLocal變量特殊的地方在於:對變量值的任何操作實際都是對這個變量在線程中的一份copy進行操作,不會影響另外一個線程中同一個ThreadLocal變量的值。

例如定義一個ThreadLocal變量,值類型為Integer:

業務鏈路監控(Google Dapper)和ThreadLocal

ThreadLocal提供的幾個主要接口:

業務鏈路監控(Google Dapper)和ThreadLocal

範例代碼:

業務鏈路監控(Google Dapper)和ThreadLocal

業務鏈路監控(Google Dapper)和ThreadLocal

業務鏈路監控(Google Dapper)和ThreadLocal

執行結果:

業務鏈路監控(Google Dapper)和ThreadLocal

結果分析:

  • thread0和thread1中對ThreadLocal變量seq的操作並沒有相互影響。
  • 主線程在thread1啟動前修改seq值對thread1無影響,thread1中seq初始值仍然是0。
  • 三個線程中調用get方法獲取到的是不同的TestBean對象

ThreadLocal變量線程獨立的原理:

直接看ThreadLocal變量的賦值:

業務鏈路監控(Google Dapper)和ThreadLocal

  • getMap的作用是返回線程對象t的threadLocals屬性的值
業務鏈路監控(Google Dapper)和ThreadLocal

線程對象的threadLocals屬性定義如下:

業務鏈路監控(Google Dapper)和ThreadLocal

getMap返回的是線程對象t的threadLocals屬性,一個ThreadLocalMap對象

  • createMap(t,value)的作用是初始化線程對象t的屬性threadLocals的值:
業務鏈路監控(Google Dapper)和ThreadLocal

  • 綜上看,ThreadLocal.set(T value)的邏輯是:
  • 首先獲取當前線程對象t,然後調用getMap(t)獲取t.threadLocals,如果獲取到的t.threadLocals為空,就調用createMap(t,value)對t.threadLocals進行初始化賦值,否則調用map.set(this,value)覆蓋t.threadLocals的值。

一個線程中調用ThreadLocal變量的get/set方法獲取和修改的是當前線程中存儲的value,當前線程無法修改另外一個線程的存儲的value,這就是ThreadLocal變量線程獨立的原因。

但是如果不同線程的value通過調用set方法指向同一個對象,ThreadLocal就喪失了線程獨立性,範例代碼:

業務鏈路監控(Google Dapper)和ThreadLocal

業務鏈路監控(Google Dapper)和ThreadLocal

業務鏈路監控(Google Dapper)和ThreadLocal

和前面代碼的區別在於,線程運行前,調用set方法將value置為外部的testBean變量,看運行結果:

業務鏈路監控(Google Dapper)和ThreadLocal

所以ThreadLocal線程獨立的前提是:不要使用set方法設置value為同一個對象,ThreadLocal對象會自動在線程第一次調用get方法中調用initialValue()方法生成一個類型的實例作為value。

ThreadLocal變量的特點是:線程獨立,生命週期和線程的生命週期一致。正是這2個特點,決定了它可以在分佈式的業務鏈路監控系統中用於Trace信息的傳輸。


分享到:


相關文章: