隨着業務愈來愈複雜,企業應用也進入了分佈式服務化的階段,傳統的日誌監控等方式沒法很好達到跟蹤調用,排查問題等需求。在谷歌論文《 Dapper,大規模分佈式系統的跟蹤系統》的指導下,許多優秀的APM應運而生。css
分佈式追蹤系統發展很快,種類繁多,給咱們帶來很大的方便。但在數據採集過程當中,有時須要侵入用戶代碼,而且不一樣系統的 API 並不兼容,這就致使了若是您但願切換追蹤系統,每每會帶來較大改動。OpenTracing爲了解決不一樣的分佈式追蹤系統 API 不兼容的問題,誕生了 OpenTracing 規範。OpenTracing 是一個輕量級的標準化層,它位於應用程序/類庫和追蹤或日誌分析程序之間。詳細介紹見 opentracing文檔中文版。html
本文要介紹的就是國人吳晟基於OpenTracking實現的開源項目skywalking(碼雲、github)。java
針對分佈式系統的APM(應用性能監控)系統,特別針對微服務、cloud native和容器化(Docker, Kubernetes, Mesos)架構, 其核心是個分佈式追蹤系統。git
性能好
針對單實例5000tps的應用,在全量採集的狀況下,只增長 10% 的CPU開銷。詳細評測見《skywalking agent performance test》。github
支持多語言探針web
支持自動及手動探針
自動探針:Java支持的中間件、框架與類庫列表
手動探針:OpenTrackingApi、@Trace註解、trackId集成到日誌中。apache
agent
探針,用來收集和發送數據到歸集器。編程
-javaagent:/path/to/skywalking-agent/skywalking-agent.jar
# 當前的應用編碼,最終會顯示在webui上。
# 建議一個應用的多個實例,使用有相同的application_code。請使用英文
agent.application_code=Your_ApplicationName
# 每三秒採樣的Trace數量
# 默認爲負數,表明在保證不超過內存Buffer區的前提下,採集全部的Trace
# agent.sample_n_per_3_secs=-1
# 設置須要忽略的請求地址
# 默認配置以下
# agent.ignore_suffix=.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg
# 探針調試開關,若是設置爲true,探針會將全部操做字節碼的類輸出到/debugging目錄下
# skywalking團隊可能在調試,須要此文件
# agent.is_open_debugging_class = true
# 對應Collector的config/application.yml配置文件中 agent_server/jetty/port 配置內容
# 例如:
# 單節點配置:SERVERS="127.0.0.1:8080"
# 集羣配置:SERVERS="10.2.45.126:8080,10.2.45.127:7600"
collector.servers=127.0.0.1:10800
# 日誌文件名稱前綴
logging.file_name=skywalking-agent.log
# 日誌文件最大大小
# 若是超過此大小,則會生成新文件。
# 默認爲300M
logging.max_file_size=314572800
# 日誌級別,默認爲DEBUG。
logging.level=DEBUG
複製代碼
collector
鏈路數據歸集器,數據能夠落地ElasticSearch,單機也能夠落地H2,不推薦,H2僅做爲臨時演示用。安全
web
web可視化平臺,用來展現落地的數據。bash
ui
單獨的開源ui項目,更美觀易用。demo
Java Agent
Java agent是用一個簡單的jar文件來表示的。跟普通的Java程序很類似,Java agent定義了一些類做爲入口點。 這些做爲入口點的類須要包含一個靜態方法,這些方法會在你本來的Java程序的main方法調用以前被調用:
class MyAgent {
public static void premain(String args, Instrumentation inst) {
// implement agent here ...
}
}
複製代碼
關於處理Java agent時最有趣的部分,是premain方法中的第二個參數。這個參數是以一個Instrumentation接口的實現類實例的形式存在的。這個接 口提供了一種機制,可以經過定義一個ClassFileTransformer,來干預對Java類的加載過程。有了這種轉設施,咱們就可以在Java類 被使用以前,去實現對類邏輯的強化。
在最基本的用例中,Java agent會用來設置應用屬性或者配置特定的環境狀態,agent可以做爲可重用和可插入的組件。以下的樣例描述了這樣的一個agent,它設置了一個系統屬性,在實際的程序中就可使用該屬性了:
public class Agent {
public static void premain(String arg) {
System.setProperty("my-property", 「foo」);
}
}
複製代碼
若是要使用這個agent,必需要將agent類和資源打包到jar中,而且在jar的manifest中要將Agent-Class屬性設置爲包含premain方法的agent類。(agent必需要打包到jar文件中,它不能經過拆解的格式進行指定。)接下來,咱們須要啓動應用程序,而且在命令行中經過javaagent參數來引用jar文件的位置:
java -javaagent:myAgent.jar -jar myProgram.jar
複製代碼
咱們還能夠在位置路徑上設置可選的agent參數。在下面的命令中會啓動一個Java程序而且添加給定的agent,將值myOptions做爲參數提供給premain方法:
java -javaagent:myAgent.jar=myOptions -jar myProgram.jar
複製代碼
經過重複使用javaagent命令,可以添加多個agent。
可是,Java agent的功能並不侷限於修改應用程序環境的狀態,Java agent可以訪問Java instrumentation API,這樣的話,agent就能修改目標應用程序的代碼。Java虛擬機中這個不爲人知的特性提供了一個強大的工具,有助於實現面向切面的編程。
若是要對Java程序進行這種修改,咱們須要在agent的premain方法上添加類型爲Instrumentation的第二個參數。Instrumentation參數能夠用來執行一系列的任務,好比肯定對象以字節爲單位的精確大小以及經過註冊ClassFileTransformers實際修改類的實現。ClassFileTransformers註冊以後,當類加載器(class loader)加載類的時候都會調用它。當它被調用時,在類文件所表明的類加載以前,類文件transformer有機會改變或徹底替換這個類文件。按照這種方式,在類使用以前,咱們可以加強或修改類的行爲,以下面的樣例所示:
public class Agent {
public static void premain(String argument, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined, // 若是類以前沒有加載的話,值爲null
ProtectionDomain protectionDomain,
byte[] classFileBuffer) {
// 返回改變後的類文件。
}
});
}
}
複製代碼
經過使用Instrumentation實例註冊上述的ClassFileTransformer以後,每一個類加載的時候,都會調用這個transformer。爲了實現這一點,transformer會接受一個二進制和類加載器的引用,分別表明了類文件以及試圖加載類的類加載器。
Java agent也能夠在Java應用的運行期註冊,若是是在這種場景下,instrumentation API容許從新定義已加載的類,這個特性被稱之爲「HotSwap」。不過,從新定義類僅限於替換方法體。在從新定義類的時候,不能新增或移除類成員,而且類型和簽名也不能進行修改。當類第一次加載的時候,並無這種限制,若是是在這樣的場景下,那classBeingRedefined會被設置爲null。
Byte Buddy
Byte Buddy是開源的、基於Apache 2.0許可證的庫,它致力於解決字節碼操做和instrumentation API的複雜性。Byte Buddy所聲稱的目標是將顯式的字節碼操做隱藏在一個類型安全的領域特定語言背後。經過使用Byte Buddy,任何熟悉Java編程語言的人都有望很是容易地進行字節碼操做。 做爲Byte Buddy的簡介,以下的樣例展示瞭如何生成一個簡單的類,這個類是Object的子類,而且重寫了toString方法,用來返回「Hello World!」。與原始的ASM相似,「intercept」會告訴Byte Buddy爲攔截到的指令提供方法實現:
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader(),
ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
複製代碼
詳見ByteBuddy做者的《Make agents, not frameworks》及譯文《構建Java Agent,而不是使用框架》。
Agent模塊
更詳細的源碼分析見芋道源碼-skywailking。