Pinpoint技術概述
注: 內容翻譯自官方文檔 Technical Overview Of Pinpoint, 內容有點長,但是強烈推薦閱讀!基本上這是目前pinpoint唯一的一份詳細介紹設計和實現(xiàn)的資料。
Pinpoint是一個分析大型分布式系統(tǒng)的平臺,提供解決方案來處理海量跟蹤數(shù)據(jù)。2012年七月開始開發(fā),2015年1月9日作為開源項目啟動。
本文將介紹Pinpoint: 什么促使我們開始搭建它, 用了什么技術, 還有Pinpoint agent是如何優(yōu)化的。
開始動機 & Pinpoint特點
和如今相比, 過去的因特網的用戶數(shù)量相對較小,而因特網服務的架構也沒那么復雜。web服務通常使用兩層(web 服務器和數(shù)據(jù)庫)或三層(web服務器,應用服務器和數(shù)據(jù)庫)架構。然而在如今,隨著互聯(lián)網的成長,需要支持大量的并發(fā)連接,并且需要將功能和服務有機結合,導致更加復雜的軟件棧組合。更確切地說,比三層層次更多的n層架構變得更加普遍。SOA或者微服務架構成為現(xiàn)實。
系統(tǒng)的復雜度因此提升。系統(tǒng)越復雜,越難解決問題,例如系統(tǒng)失敗或者性能問題。在三層架構中找到解決方案還不是太難,僅僅需要分析3個組件比如web服務器,應用服務器和數(shù)據(jù)庫,而服務器數(shù)量也不多。但是,如果問題發(fā)生在n層架構中,就需要調查大量的組件和服務器。另一個問題是僅僅分析單個組件很難看到大局;當發(fā)生一個低可見度的問題時,系統(tǒng)復雜度越高,就需要更長的時間來查找原因。最糟糕的是,某些情況下我們甚至可能無法查找出來。
這樣的問題也發(fā)生在NAVER的系統(tǒng)中。使用了大量工具如應用性能管理(APM)但是還不足以有效處理問題。因此我們最終決定為n層架構開發(fā)新的跟蹤平臺,為n層架構的系統(tǒng)提供解決方案。
Pinpoint, 2012年七月開始開發(fā),在2015年1月作為一個開源項目啟動, 是一個為大型分布式系統(tǒng)服務的n層架構跟蹤平臺。 Pinpoint的特點如下:
- 分布式事務跟蹤,跟蹤跨分布式應用的消息
- 自動檢測應用拓撲,幫助你搞清楚應用的架構
- 水平擴展以便支持大規(guī)模服務器集群
- 提供代碼級別的可見性以便輕松定位失敗點和瓶頸
- 使用字節(jié)碼增強技術,添加新功能而無需修改代碼
本文將講述Pinpoint的技術,例如事務跟蹤和字節(jié)碼增強。還會解釋應用在pinpoint agent中的優(yōu)化方法,agent修改字節(jié)碼并記錄性能數(shù)據(jù)。
分布式事務跟蹤,基于google Dapper
pinpoint跟蹤單個事務中的分布式請求,基于google Dapper。
在Google Dapper中分布式事務追蹤是如何工作的
當一個消息從Node1發(fā)送到Node2(見圖1)時,分布式追蹤系統(tǒng)的核心是在分布式系統(tǒng)中識別在Node1中處理的消息和在Node2中出的消息之間的關系。

問題在于無法在消息之間識別關系。例如,我們無法識別從Node1發(fā)送的第N個消息和Node2接收到的N'消息之間的關系。換句話說,當Node1發(fā)送完第X個消息時,是無法在Node2接收到的N的消息里面識別出第X個消息的。有一種方式試圖在TCP或者操作系統(tǒng)層面追蹤消息。但是,實現(xiàn)很復雜而且性能低下,而且需要為每個協(xié)議單獨實現(xiàn)。另外,很難精確追蹤消息。
不過,Google dapper實現(xiàn)了一個簡單的解決方案來解決這個問題。這個解決方案通過在發(fā)送消息時添加應用級別的標簽作為消息之間的關聯(lián)。例如,在HTTP請求中的HTTP header中為消息添加一個標簽信息并使用這個標簽跟蹤消息。
Google's Dapper
關于Google Dapper的更多信息, 請見 "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure."
Pinpoint基于google dapper的跟蹤技術,但是已經修改為在調用的header中添加應用級別標簽數(shù)據(jù)以便在遠程調用中跟蹤分布式事務。標簽數(shù)據(jù)由多個key組成,定義為TraceId。
Pinpoint中的數(shù)據(jù)結構
Pinpoint中,核心數(shù)據(jù)結構由Span, Trace, 和 TraceId組成。
- Span: RPC (遠程過程調用/remote procedure call)跟蹤的基本單元; 當一個RPC調用到達時指示工作已經處理完成并包含跟蹤數(shù)據(jù)。為了確保代碼級別的可見性,Span擁有帶SpanEvent標簽的子結構作為數(shù)據(jù)結構。每個Span包含一個TraceId。
- Trace: 多個Span的集合; 由關聯(lián)的RPC (Spans)組成. 在同一個trace中的span共享相同的TransactionId。Trace通過SpanId和ParentSpanId整理為繼承樹結構.
- TraceId: 由 TransactionId, SpanId, 和 ParentSpanId 組成的key的集合. TransactionId 指明消息ID,而SpanId 和 ParentSpanId 表示RPC的父-子關系。
- TransactionId (TxId): 在分布式系統(tǒng)間單個事務發(fā)送/接收的消息的ID; 必須跨整個服務器集群做到全局唯一.
- SpanId: 當收到RPC消息時處理的工作的ID; 在RPC請求到達節(jié)點時生成。
- ParentSpanId (pSpanId): 發(fā)起RPC調用的父span的SpanId. 如果節(jié)點是事務的起點,這里將沒有父span - 對于這種情況, 使用值-1來表示這個span是事務的根span。
Google Dapper 和 NAVER Pinpoint在術語上的不同
Pinpoint中的術語"TransactionId"和google dapper中的術語"TraceId"有相同的含義。而Pinpoint中的術語"TraceId"引用到多個key的集合。
TraceId如何工作
下圖描述TraceId的行為,在4個節(jié)點之間執(zhí)行了3次的RPC調用:

在圖2中,TransactionId (TxId) 體現(xiàn)了三次不同的RPC作為單個事務被相互關聯(lián)。但是,TransactionId 本身不能精確描述PRC之間的關系。為了識別PRC之間的關系,需要SpanId 和 ParentSpanId (pSpanId). 假設一個節(jié)點是Tomcat,可以將SpanId想象為處理HTTP請求的線程,ParentSpanId代表發(fā)起這個RPC調用的SpanId.
使用TransactionId,Pinpoint可以發(fā)現(xiàn)關聯(lián)的n個Span,并使用SpanId和ParentSpanId將這n個span排列為繼承樹結構。
SpanId 和 ParentSpanId 是 64位長度的整型??赡馨l(fā)生沖突,因為這個數(shù)字是任意生成的,但是考慮到值的范圍可以從-9223372036854775808到9223372036854775807,不太可能發(fā)生沖突. 如果key之間出現(xiàn)沖突,Pinpoint和Google Dapper系統(tǒng),會讓開發(fā)人員知道發(fā)生了什么,而不是解決沖突。
TransactionId 由 AgentIds, JVM (java虛擬機)啟動時間, 和 SequenceNumbers/序列號組成.
- AgentId: 當Jvm啟動時用戶創(chuàng)建的ID; 必須在pinpoinit安裝的全部服務器集群中全局唯一. 最簡單的讓它保持唯一的方法是使用hostname($HOSTNAME),因為hostname一般不會重復. 如果需要在服務器集群中運行多個JVM,請在hostname前面增加一個前綴來避免重復。
- JVM 啟動時間: 需要用來保證從0開始的SequenceNumber的唯一性. 當用戶錯誤的創(chuàng)建了重復的AgentId時這個值可以用來預防ID沖突。
- SequenceNumber: Pinpoint agent 生成的ID, 從0開始連續(xù)自增;為每個消息生成一個.
Dapper 和 Zipkin, Twitter的一個分布式系統(tǒng)跟蹤平臺, 生成隨機TraceIds (Pinpoint是TransactionIds) 并將沖突情況視為正常。然而, 在Pinpiont中我們想避免沖突的可能,因此實現(xiàn)了上面描述的系統(tǒng)。有兩種選擇:一是數(shù)據(jù)量小但是沖突的可能性高,二是數(shù)據(jù)量大但是沖突的可能性低。我們選擇了第二種。
可能有更好的方式來處理transaction。我們起先有一個想法,通過中央key服務器來生成key。如果實現(xiàn)這個模式,可能導致性能問題和網絡錯誤。因此,大量生成key被考慮作為備選。后面這個方法可能被開發(fā)?,F(xiàn)在采用簡單方法。在pinpoint中,TransactionId被當成可變數(shù)據(jù)來對待。
字節(jié)碼增強,無需代碼修改
前面我們解釋了分布式事務跟蹤。實現(xiàn)的方法之一是開發(fā)人員自己修改代碼。當發(fā)生RPC調用時容許開發(fā)人員添加標簽信息。但是,修改代碼會成為包袱,即使這樣的功能對開發(fā)人員非常有用。
Twitter的 Zipkin 使用修改過的類庫和它自己的容器(Finagle)來提供分布式事務跟蹤的功能。但是,它要求在需要時修改代碼。我們期望功能可以不修改代碼就工作并希望得到代碼級別的可見性。為了解決這個問題,pinpoint中使用了字節(jié)碼增強技術。Pinpoint agent干預發(fā)起RPC的代碼以此來自動處理標簽信息。
克服字節(jié)碼增強的缺點
字節(jié)碼增強在手工方法和自動方法兩者之間屬于自動方法。
- 手工方法: 開發(fā)人員使用ponpoint提供的API在關鍵點開發(fā)記錄數(shù)據(jù)的代碼
- 自動方法: 開發(fā)人員不需要代碼改動,因為pinpoint決定了哪些API要調節(jié)和開發(fā)
下面是每個方法的優(yōu)點和缺點:
Table1 每個方法的優(yōu)缺點
| 優(yōu)點 | 缺點 | |
|---|---|---|
| 手工跟蹤 | 1. 要求更少開發(fā)資源 2. API可以更簡單并最終減少bug的數(shù)量 | 1. 開發(fā)人員必須修改代碼 2. 跟蹤級別低 |
| 自動跟蹤 | 1. 開發(fā)人員不需要修改代碼 2. 可以收集到更多精確的數(shù)據(jù)因為有字節(jié)碼中的更多信息 | 1. 在開發(fā)pinpoint時,和實現(xiàn)一個手工方法相比,需要10倍開銷來實現(xiàn)一個自動方法 2. 需要更高能力的開發(fā)人員,可以立即識別需要跟蹤的類庫代碼并決定跟蹤點 3. 增加bug發(fā)生的可能性,因為使用了如字節(jié)碼增強這樣的高級開發(fā)技巧 |
字節(jié)碼增強是一種高難度和高風險的技術。但是,綜合考慮使用這種技術開所需要的資源和難度,使用它仍然有很多的益處。
雖然它需要大量的開發(fā)資源,在開發(fā)服務上它需要很少的資源。例如,下面展示了使用字節(jié)碼增強的自動方法和使用類庫的手工方法(在這里的上下文中,開銷是為澄清而假設的隨機數(shù))之間的開銷。
-
自動方法: 總共 100
- Pinpoint開發(fā)開銷: 100
- 服務實施的開銷: 0
-
手工方法: 總共 30
- Pinpoint開發(fā)開銷: 20
- 服務實施的開銷: 10
上面的數(shù)據(jù)告訴我們手工方法比自動方法有更合算。但是,不適用于我們的在NAVER的環(huán)境。在NAVER我們有幾千個服務,因此在上面的數(shù)據(jù)中需要修改用于服務實施的開銷。如果我們有10個服務需要修改,總開銷計算如下:
Pinpoint開發(fā)開銷 20 + 服務實施開銷 10 x 10 = 120
基于這個結果,自動方法是一個更合算的方式。
我們很幸運的在pinpoint團隊中擁有很多高能力而專注于Java的開發(fā)人員。因此,我們相信克服pinpoint開發(fā)中的技術難題只是個時間問題。
字節(jié)碼增強的價值
我們選擇字節(jié)碼增強的理由,除了前面描述的那些外,還有下面的強有力的觀點:
隱藏API
一旦API被暴露給開發(fā)人員使用,我們作為API的提供者,就不能隨意的修改API。這樣的限制會給我們增加壓力。
我們可能修改API來糾正錯誤設計或者添加新的功能。但是,如果做這些受到限制,對我們來說很難改進API。解決這個問題的最好的答案是一個可升級的系統(tǒng)設計,而每個人都知道這不是一個容易的選擇。如果我們不能掌控未來,就不可能創(chuàng)建完美的API設計。
而使用字節(jié)碼增強技術,我們就不必擔心暴露跟蹤API而可以持續(xù)改進設計,不用考慮依賴關系。對于那些計劃使用pinpoint開發(fā)應用的人,換一句話說,這代表對于pinpoint開發(fā)人員,API是可變的?,F(xiàn)在,我們將保留隱藏API的想法,因為改進性能和設計是我們的第一優(yōu)先級。
容易啟用或者禁用
使用字節(jié)碼增強的缺點是當Pinpoint自身類庫的采樣代碼出現(xiàn)問題時可能影響應用。不過,可以通過啟用或者禁用pinpoint來解決問題,很簡單,因為不需要修改代碼。
通過增加下面三行到JVM啟動腳本中就可以輕易的為應用啟用pinpoint:
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=<Agent's UniqueId>
-Dpinpoint.applicationName=<The name indicating a same service (AgentId collection)>
如果因為pinpoint發(fā)生問題,只需要在JVM啟動腳本中刪除這些配置數(shù)據(jù)。
字節(jié)碼如何工作
由于字節(jié)碼增強技術處理java字節(jié)碼, 有增加開發(fā)風險的趨勢,同時會降低效率。另外,開發(fā)人員更容易犯錯。在pinpoint,我們通過抽象出攔截器(interceptor)來改進效率和可達性(accessibility)。pinpoint在類裝載時通過介入應用代碼為分布式事務和性能信息注入必要的跟蹤代碼。這會提升性能,因為代碼注入是在應用代碼中直接實施的。

在pinpoint中,攔截器API在性能數(shù)據(jù)被記錄的地方分開(separated)。為了跟蹤,我們添加攔截器到目標方法使得before()方法和after()方法被調用,并在before()方法和after()方法中實現(xiàn)了部分性能數(shù)據(jù)的記錄。使用字節(jié)碼增強,pinpoint agent可以記錄需要方法的數(shù)據(jù),只有這樣采樣數(shù)據(jù)的大小才能變小。
pinpoint agent的性能優(yōu)化
最后,我們描述用于pinpoint agent的性能優(yōu)化的方式。
使用二進制格式(thrift)
通過使用二進制格式(thrift)可以提高編碼速度,雖然它使用和調試要難一些。也有利于減少網絡使用,因為生成的數(shù)據(jù)比較小。
使用變長編碼和格式優(yōu)化數(shù)據(jù)記錄
如果將一個長整型轉換為固定長度的字符串, 數(shù)據(jù)大小一般是8個字節(jié)。然而,如果你用變長編碼,數(shù)據(jù)大小可以是從1到10個字符,取決于給定數(shù)字的大小。為了減小數(shù)據(jù)大小,pinpoint使用thrift的CompactProtocol協(xié)議(壓縮協(xié)議)來編碼數(shù)據(jù),因為變長字符串和記錄數(shù)據(jù)可以為編碼格式做優(yōu)化。pinpoint agent通過基于跟蹤的根方法的時間開始來轉換其他的時間來減少數(shù)據(jù)大小。
圖4 說明了上面章節(jié)描述的想法:

為了得到關于三個不同方法(見圖4)被調用時間的數(shù)據(jù),不得不在6個不同的點上測量時間,用固定長度編碼這需要48個字節(jié)(6 * 8)。
以此同時,pinpoint agent 使用可變長度編碼并根據(jù)對應的格式記錄數(shù)據(jù)。然后在其他時間點通過和參考點比較來計算時間值(在vector中),根方法的起點被確認為參考點。這只需要占用少量的字節(jié),因為vector使用小數(shù)字。圖4中消耗了13個字節(jié)。
如果執(zhí)行方法花費了更多時間,即使使用可變長度編碼也會增加字節(jié)數(shù)量。但是,依然比固定長度編碼更有效率。
用常量表替換重復的API信息,SQL語句和字符串
我們希望pinpoint能開啟代碼級別的跟蹤。然而,存在增大數(shù)據(jù)大小的問題。每次高精度的數(shù)據(jù)被發(fā)送到服務器將增大數(shù)據(jù)大小,導致增加網絡使用。
為了解決這個問題,我們使用了在遠程HBase中創(chuàng)建常量表的策略。例如,每次調用"Method A"的信息被發(fā)送到pinpoint collector, 數(shù)據(jù)大小將很大。pinpoint agent 用一個ID替換"method A",在HBase中作為一個常量表保存ID和"method A"的信息,然后用ID生成跟蹤數(shù)據(jù)。然后當用戶在網站上獲取跟蹤數(shù)據(jù)時,pinpoint web在常量表中搜索對應ID的方法信息并組合他們。使用同樣的方式來減少SQL或者頻繁使用的字符串的數(shù)據(jù)大小。
處理大量請求的采樣
我們在線門戶服務有海量請求。單個服務每天處理超過200億請求。容易跟蹤這樣的請求:方法是添加足夠多的網絡設施和服務器來跟蹤請求并擴展服務器來收集數(shù)據(jù)。然后,這不是處理這種場景的合算的方法,僅僅是浪費金錢和資源。
在Pinpoint,可以收集采樣資料而不必跟蹤每個請求。在開發(fā)環(huán)境中請求量很小,每個數(shù)據(jù)都收集。而在產品環(huán)境請求量巨大,收集小比率的數(shù)據(jù)如1~5%,足夠檢查整個應用的狀態(tài)。有采樣后,可以最小化應用的網絡開銷并降低諸如網絡和服務器的設施費用。
pinpoint采樣方法
Pinpoint 支持計數(shù)采樣,如果設置為10則只采樣10分之一的請求。我們計劃增加新的采樣器來更有效率的收集數(shù)據(jù)。
注:對應的配置項在agent下的pinpoint.config文件中,默認"profiler.sampling.rate=1"表示全部
使用異步數(shù)據(jù)傳輸來最小化應用線程中止
pinpoint不阻塞應用線程,因為編碼后的數(shù)據(jù)或者遠程消息被其他線程異步傳輸。
使用UDP傳輸數(shù)據(jù)
和gogole dapper不同,pinpoint通過網絡傳輸數(shù)據(jù)來確保數(shù)據(jù)速度。作為一個服務間使用的通用設施,當數(shù)據(jù)通訊持續(xù)突發(fā)時網絡會成為問題。在這種情況下,pinpoint agent使用UDP協(xié)議來給服務讓出網絡連接優(yōu)先級。
注意
數(shù)據(jù)傳輸API可以被替換,因為它是接口分離的??梢孕薷膶崿F(xiàn)為用其他方式存儲數(shù)據(jù),比如本地文件。
pinpoint應用示例
這里給出一個例子關于如何從應用獲取數(shù)據(jù),這樣就可以全面的理解前面講述的內容。
圖5 展示了當在 TomcatA 和 TomcatB 中安裝pinpoint的數(shù)據(jù)。可以把單個節(jié)點的跟蹤數(shù)據(jù)看成single traction,提現(xiàn)分布式事務跟蹤的流程。

下面闡述pinpoint為每個方法做了什么:
-
當請求到達TomcatA時, Pinpoint agent 產生一個 TraceId.
- TX_ID: TomcatA^TIME^1
- SpanId: 10
- ParentSpanId: -1(Root)
從spring MVC 控制器中記錄數(shù)據(jù)
-
插入HttpClient.execute()方法的調用并在HTTPGet中配置TraceId
-
創(chuàng)建一個子TraceId
- TX_ID: TomcatA^TIME^1 -> TomcatA^TIME^1
- SPAN_ID: 10 -> 20
- PARENT_SPAN_ID: -1 -> 10 (父 SpanId)
-
在HTTP header中配置子 TraceId
- HttpGet.setHeader(PINPOINT_TX_ID, "TomcatA^TIME^1")
- HttpGet.setHeader(PINPOINT_SPAN_ID, "20")
- HttpGet.setHeader(PINPOINT_PARENT_SPAN_ID, "10")
-
-
傳輸打好tag的請求到TomcatB.
-
TomcatB 檢查傳輸過來的請求的header
HttpServletRequest.getHeader(PINPOINT_TX_ID)
-
TomcatB 作為子節(jié)點工作因為它識別了header中的TraceId
- TX_ID: TomcatA^TIME^1
- SPAN_ID: 20
- PARENT_SPAN_ID: 10
-
從spring mvc控制器中記錄數(shù)據(jù)并完成請求

- 當從tomcatB回來的請求完成時,pinpoint agent發(fā)送跟蹤數(shù)據(jù)到pinpoint collector就此存儲在HBase中
- 在對tomcatB的HTTP調用結束后,TomcatA的請求也完成了。pinpoint agent發(fā)送跟蹤數(shù)據(jù)到pinpoint collector就此存儲在HBase中
- UI從HBase中讀取跟蹤數(shù)據(jù)并通過排序樹來創(chuàng)建調用棧
結論
pinpoint是和應用一起運行的另外的應用。使用字節(jié)碼增強使得pinpoint看上去不需要代碼修改。通常,字節(jié)碼增強技術讓應用容易造成風險。如果問題發(fā)生在pinpoint中,它會影響應用。目前,我們專注于改進pinpoint的性能和設計,而不是移除這樣的威脅,因為我們任務這些讓pinpoint更加有價值。因此你需要決定是否使用pinpoint。
我們還是有大量的工作需要完成來改進pinpoint,盡管不完整,pinpoint還是作為開源項目發(fā)布了。我們將持續(xù)努力開發(fā)并改進pinpoint以便滿足你的期望。
Woonduk Kang編寫
在2011年, 關于我自己我這樣寫到 — 作為一個開發(fā)人員,我想開發(fā)人們愿意付款的軟件程序,像Microsoft 或者 Oracle. 當Pinpoint被作為一個開源項目啟動,看上去我的夢想稍微實現(xiàn)了一點。目前, 我的愿望是讓pinpoint對用戶更加有價值和更惹人喜歡.