個人博客原文地址:http://www.ltang.me/2016/12/09/pinpoint-plugin-study/
主要節選翻譯自官方文檔 Pinpoint Plugin Developer Guide
在pinpoint裏面,一個“事務”(或者說請求?)是由一組spans構成的。每個span代表了請求經歷的一個單獨的邏輯節點。
一個span記錄了重要方法的信息和它們的相關數據,請求,返回值等,概括它們爲spanEvents. 一個span和它包含的所有spanEvents代表了一個方法調用。
PinPoint插件結構
pinpoint插件是由TraceMetadataProvider
和ProfilerPlugin
的實現組成。TraceMetadataProvider的實現提供了ServiceType和AnnotationKey給PinPoint agent/web/collector. ProfilerPlugin的實現被agent使用,用來修改目標類,記錄跟蹤數據。
插件放在agent的plugin
目錄,web和collector的WEB-INF/lib
目錄。插件需要在資源文件夾的META-INF/services
目錄中,聲明自己實現的TraceMetadataProvider和ProfilerPlugin。
TraceMetadataProvider
ServiceType
每個Span
和SpanEvent
都包含了ServiceType, 代表了正在追蹤的方法屬於誰。web也需要通過serviceType來展示。
需要注意的是,每個ServiceType由name,code,description,properties組成。其中,code是不能亂用的,每個serviceType的code唯一。因此,pinpoint預留了一部分code範圍供我們自己開發使用。
AnnotationKey
同樣的,AnnotationKey也有自己的唯一code, 900-999是pinpoint團隊爲我們預留的。
ProfilerPlugin
profilerPlugin修改目標類以收集跟蹤數據。
它工作的步驟是:
1. jvm 啓動時啓動pinpoint agent
2. agent加載插件目錄下所有的插件
3. agent調用每個插件的ProfilerPlugin.setup(ProfilerPluginsetupContext)方法
4. 在setup方法裏面,插件決定是否要轉換類,並且註冊一個transformerCallerBack
5. 應用啓動
6. 每次類加載的時候,agent會尋找註冊在該類上的TransformerCallback
7. 如果找到了,agent會調用callback裏面的doInTransform方法
8. TransformerCallerBack會修改目標類的字節碼,添加攔截,添加字段等等
9. 修改後的字節碼被返回給jvm,然後類被加載
10. 應用繼續
11. 當一個修改後的方法被調用時,注入的攔截器before
和after
方法會被調用
12. 攔截器紀錄追蹤數據
最重要的是考慮:
1. 找出哪些方法可以足夠確保追蹤
2. 注入攔截器,追蹤方法
攔截器之間甚至可能相互協作,交換上下文。這需要我們自己去考慮。
Plain method 簡單方法
Top level method 頂層方法
一個節點的頂層方法是它的攔截器開始新的追蹤的地方。典型的方法就是rpc的攔截器,追蹤被標記爲帶着servicetype
的span
. 至於怎麼記錄span,取決於在這個節點之前的其他節點,“事務”是否已經開始記錄。
新的事務
如果這個節點是記錄本次事務的第一個節點,那麼你必須聲明一個新的transaction id並且紀錄它。TraceContext.newTraceObject()
會自動處理這個任務,調用即可。
接力事務
如果請求從另一個pinpoint agent追蹤的節點過來,那麼應該已經存在transaction id信息,你需要記錄下面的數據給span。(大多數數據是從上一個節點發過來的,存在請求信息裏)
name | description |
---|---|
transactionId | Transaction ID |
parentSpanId | Span ID of the previous node |
parentApplicationName | Application name of the previous node |
parentApplicationType | Application type of the previous node |
rpc | Procedure name (Optional) |
endPoint | Server(current node) address |
remoteAddr | Client address |
acceptorHost | Server address that the client used |
pinpoint通過acceptorHost找到節點間的調用關係。大多數情況下,endpoint和acceptorHost應該是一樣的,然而有時候會不同,比如通過代理。在這種情況下,你需要記錄客戶端實際發送請求去的那個地址作爲acceptorHost.一般來說,客戶端插件會將地址和事務信息添加到請求信息中。
methods invoking a remote node 調用遠程節點的方法
一個調用遠程節點的方法的攔截器必須記錄以下數據:
name | description |
---|---|
endPoint | target server address |
destinationId | Logical name of the target |
rpc | invoking target procedure name(optional) |
nextSpanId | span id that will be used by next node’s span(if next node is traceable by pinpoint) |
下一個節點是否可追蹤,影響了攔截器的實現。是否可追蹤在這裏意味着可能性。比如,一個http客戶端的下一個節點是http服務器。pinpoint並不追蹤所有的http服務器,但是是有可能追蹤的。在這種情況下,http客戶端的下一個節點就是可追蹤的。另一方面,mysql jdbc的下一個節點,mysql 數據庫,是不可追蹤的。
如果下一節點可追蹤
如果下一個節點可追蹤,那麼需要傳遞下面的數據給下一節點。怎麼傳遞是協議獨立的,最差的情況下不能傳遞。
name | description |
---|---|
transactionId | Transaction ID |
parentApplicationName | Application name of current node |
parentApplicationType | Application type of current node |
parentSpanId | span id of trace at current node |
nextSpanId | Span id that will be used by the next node’s span(same value with nextSpanId of above table) |
pinpoint通過匹配destinationId和acceptorHost來找到調用關係。因此,客戶端插件需要記錄destinationId, 服務端插件需要用同樣的值記錄acceptorHost。如果服務端沒法自己拿到這個值,那麼客戶端需要將這個值傳遞給服務端。
攔截器記錄的ServiceType必須來自RPC客戶端分類。
如果下一個節點不可追蹤
如果下一個節點不可追蹤,ServiceType必須有TERMINAL
屬性。
如果你想記錄destinationId, 必須有INCLUDE_DESTINATION_ID
屬性.如果你記錄了destinationId, server map會爲每個destinationId展示一個節點,即使它們的endPoint一樣。
異步任務
異步任務意味着初始化任務的線程和處理任務的線程不是同一個。如果想追蹤異步任務,必須給兩個方法創建攔截器 1)初始化任務 2)真正處理任務
初始化方法的攔截器必須分發一個AsyncTraceId 並且傳遞給處理方法。至於怎麼傳遞需要根據目標庫來確定,有可能根本傳不了。
處理方法需要使用這個傳遞過來的AsyncTraceId繼續跟蹤。你不需要手動處理這種傳遞,只需要簡單地擴展SpanAsyncEventSimpleAroundInterceptor 來寫攔截器就足夠了。但爲了初始化這個傳遞你需要注入一個字段,使用AsyncTraceIdAccessor,到處理方法所在的類,並設置AsyncTraceId 到這個字段,在處理方法調用之前。
案例學習:http
HTTP客戶端是一個“方法調用遠程節點”的例子,http服務器是一個頂層節點方法。如上所說,客戶端插件必須找到辦法將事務數據傳遞給服務端插件來繼續這種追蹤。
1. 使用http頭來傳遞事務信息。
2. 客戶端插件記錄 ip:port
作爲服務端的destinationId
3. 客戶端插件將destinationId值作爲Header.HTTP_HOST傳遞給server
4. 服務端插件將Header.HTTP_HOST作爲acceptorHost記錄