調用鏈的興起
- 分佈式遇到的問題:隨着微服務設計理念在系統中的應用,業務的調用鏈越來越複雜。一個請求可能會涉及到幾十個服務的協同操作,涉及到多個團隊的業務系統。當遇到問題需要定位時候,也會產生一系列的麻煩。
- 解決方案:通過調用連,把一次請求調用過程完整的串聯起來,實現了對請求調用路徑的監控,便於故障快速定位。
- 調用鏈顯示內容:各個調用環節的性能分析(如各個API使用時間、使用堆棧情況)、在調用連各個環節依賴關係還原、SQL語句打印、IP顯示等。
各大公司的調用鏈框架
- Google: Dapper
- 淘寶 鷹眼
- 京東 hyfra(九頭蛇) 希臘神話中的一種,京東開發平臺是宙斯,基於dubbo來實現。
調用鏈原理
- 請求到來生成一個全局TraceID,通過TraceID可以串聯起整個調用鏈,一個TraceID代表一次請求。
- 除了TraceID外,還需要SpanID用於記錄調用父子關係。每個服務會記錄下Parent id和Span id,通過他們可以組織一次完整調用鏈的父子關係。
- 一個沒有Parent id的span成爲root span,可以看成調用鏈入口。
- 所有這些ID可用全局唯一的64位整數表示;
- 整個調用過程中每個請求都要透傳TraceID和SpanID。
- 每個服務將該次請求附帶的TraceID和附帶的SpanID作爲Parent id記錄下,並且將自己生成的SpanID也記錄下。
- 要查看某次完整的調用則只要根據TraceID查出所有調用記錄,然後通過Parent id和Span id組織起整個調用父子關係。
調用鏈實現技術
採用字節碼插樁方式,監聽JVM虛擬機。好處是對代碼侵入爲零。
首先需要了解JVM的classLoader加載機制。
- JVM的classLoader主要分爲兩類,一種是初始加載器,用來加載lib下的所有後綴爲.jar的文件。另一種是所有其他類型的加載器,包括自定義的class Loader.
- 加載機制採用雙親委派模式,就是所有的加載器需要加載的時候都會先交給父類去加載,只有當父類加載不了,纔會使用自身的加載功能,如此一來,所有的加載都繞回了初始加載器。
- 例如object類定義在rt.jar中,所有的對象都是Object,都需要先加載Object類,進而都需要加載rt.jar文件,進而需要使用初始加載器來加載。
Javassist
- Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。其主要的優點,在於簡單,而 且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成。
- 作用:AOP動態代理。獲取訪問類結構信息,如參數名稱等。運行時監控插樁埋點。
- 使用流程:
- 構建ClassPool對象,找到要插入類的路徑,insertClassPath().
- 獲取CTclass, 找到已存在的類get,構建新類makeClass
- 修改構造CTclass,添加屬性 addField,添加修改方法addMethod().
- 將修改完成的class生成新的class toByteCode(),裝載該class toClass
多級嵌套事件
埋點就是事件捕獲,
字節碼插樁用到的技術:
javaagent 代理攔截 插樁的入口
javassit字節碼修改工具
java.java-->編譯-->class文件--->JVM--->字節碼
直接寫JVM 指令碼
JVM ClassLoader加載順序
1.整體採用雙親委派模式,從下往上開始依次check,如果父類存在就加載。所以最終加載順序如下:
- Bootstrap ClassLoader,引導類 , Jre_home下lib目錄下的幾個jar隨着JVM的啓動器先加載。就類似於下圖(1)。例如rt.jar,裏面存的是Object
- Ext ClassLoader 擴展類,Jre_home/lib/ext目錄下開始加載。就類似於下圖(2)。他們的啓動都依賴於圖1.
- App ClassLoader ,類似於下圖(3),容器基礎類,會加載容器的bin目錄,一般只加載兩個jar.
- Common ClassLoader 類似於圖(4),容器類
- Share ClassLoader ,容器下項目通用的類,
- WebApp ClassLoader 加載 項目/WEB-INF/lib下的jar,然後在加載classes/下的代碼。項目自定義類。
圖(1)
圖(2)
- 圖(3)圖(4)
在web項目中,自定義的jar包怎麼啓動
自己打好的jar包怎麼啓動:
在JVM參數中 -javaagent: 目錄/xx.jar。這種方式會將該jar加載到相當於圖3的位置。
Servlet 的jar加載是在圖4的位置。
這樣就涉及到一個問題,自定義的字節碼插裝類,會早於servlet的類加載,會出現class no found。
解決方案:
改變加載順序,將自定義的DispatcherServletCollecct 類,填充到Common ClassLoader層級,讓其跟servlet處於同一層級。
pool.get("com.bit.javassist.DispatcherServletCollecct").toClass(loader,null);
這樣,DispatcherServletCollecct會同時出現在圖4與圖3的位置。
http協議會加載圖4位置的類,而不會去找圖3的,因爲 tomcat的加載機制與JDK加載機制不同,tomcat當看到圖3中出現了該類,就不管了,不會繼續向上請求加載。