Pinpoint技術概述

Pinpoint是一個分析大型分佈式系統的平臺,提供解決方案來處理海量跟蹤數據。2012年七月開始開發,2015年1月9日做爲開源項目啓動。html

本文將介紹Pinpoint: 什麼促使咱們開始搭建它, 用了什麼技術, 還有Pinpoint agent是如何優化的。java

開始動機 & Pinpoint特色

和現在相比, 過去的因特網的用戶數量相對較小,而因特網服務的架構也沒那麼複雜。web服務一般使用兩層(web 服務器和數據庫)或三層(web服務器,應用服務器和數據庫)架構。然而在現在,隨着互聯網的成長,須要支持大量的併發鏈接,而且須要將功能和服務有機結合,致使更加複雜的軟件棧組合。更確切地說,比三層層次更多的n層架構變得更加廣泛。SOA或者微服務架構成爲現實。git

系統的複雜度所以提高。系統越複雜,越難解決問題,例如系統失敗或者性能問題。在三層架構中找到解決方案還不是太難,僅僅須要分析3個組件好比web服務器,應用服務器和數據庫,而服務器數量也很少。可是,若是問題發生在n層架構中,就須要調查大量的組件和服務器。另外一個問題是僅僅分析單個組件很難看到大局;當發生一個低可見度的問題時,系統複雜度越高,就須要更長的時間來查找緣由。最糟糕的是,某些狀況下咱們甚至可能沒法查找出來。github

這樣的問題也發生在NAVER的系統中。使用了大量工具如應用性能管理(APM)可是還不足以有效處理問題。所以咱們最終決定爲n層架構開發新的跟蹤平臺,爲n層架構的系統提供解決方案。web

Pinpoint, 2012年七月開始開發,在2015年1月做爲一個開源項目啓動, 是一個爲大型分佈式系統服務的n層架構跟蹤平臺。 Pinpoint的特色以下:spring

  • 分佈式事務跟蹤,跟蹤跨分佈式應用的消息
  • 自動檢測應用拓撲,幫助你搞清楚應用的架構
  • 水平擴展以便支持大規模服務器集羣
  • 提供代碼級別的可見性以便輕鬆定位失敗點和瓶頸
  • 使用字節碼加強技術,添加新功能而無需修改代碼

本文將講述Pinpoint的技術,例如事務跟蹤和字節碼加強。還會解釋應用在pinpoint agent中的優化方法,agent修改字節碼並記錄性能數據。數據庫

分佈式事務跟蹤,基於google Dapper

pinpoint跟蹤單個事務中的分佈式請求,基於google Dapper。apache

在Google Dapper中分佈式事務追蹤是如何工做的

當一個消息從Node1發送到Node2(見圖1)時,分佈式追蹤系統的核心是在分佈式系統中識別在Node1中處理的消息和在Node2中出的消息之間的關係。bootstrap

圖1. 分佈式系統中的消息關係tomcat

問題在於沒法在消息之間識別關係。例如,咱們沒法識別從Node1發送的第N個消息和Node2接收到的N'消息之間的關係。換句話說,當Node1發送完第X個消息時,是沒法在Node2接收到的N的消息裏面識別出第X個消息的。有一種方式試圖在TCP或者操做系統層面追蹤消息。可是,實現很複雜並且性能低下,並且須要爲每一個協議單獨實現。另外,很難精確追蹤消息。

不過,Google dapper實現了一個簡單的解決方案來解決這個問題。這個解決方案經過在發送消息時添加應用級別的標籤做爲消息之間的關聯。例如,在HTTP請求中的HTTP header中爲消息添加一個標籤信息並使用這個標籤跟蹤消息。

Google's Dapper

關於Google Dapper的更多信息, 請見 "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure."

Pinpoint基於google dapper的跟蹤技術,可是已經修改成在調用的header中添加應用級別標籤數據以便在遠程調用中跟蹤分佈式事務。標籤數據由多個key組成,定義爲TraceId。

Pinpoint中的數據結構

Pinpoint中,核心數據結構由Span, Trace, 和 TraceId組成。

  • Span: RPC (遠程過程調用/remote procedure call)跟蹤的基本單元; 當一個RPC調用到達時指示工做已經處理完成幷包含跟蹤數據。爲了確保代碼級別的可見性,Span擁有帶SpanEvent標籤的子結構做爲數據結構。每一個Span包含一個TraceId。
  • Trace: 多個Span的集合; 由關聯的RPC (Spans)組成. 在同一個trace中的span共享相同的TransactionId。Trace經過SpanId和ParentSpanId整理爲繼承樹結構.
  • TraceId: 由 TransactionId, SpanId, 和 ParentSpanId 組成的key的集合. TransactionId 指明消息ID,而SpanId 和 ParentSpanId 表示RPC的父-子關係。
    • TransactionId (TxId): 在分佈式系統間單個事務發送/接收的消息的ID; 必須跨整個服務器集羣作到全局惟一.
    • SpanId: 當收到RPC消息時處理的工做的ID; 在RPC請求到達節點時生成。
    • ParentSpanId (pSpanId): 發起RPC調用的父span的SpanId. 若是節點是事務的起點,這裏將沒有父span - 對於這種狀況, 使用值-1來表示這個span是事務的根span。

Google Dapper 和 NAVER Pinpoint在術語上的不一樣

Pinpoint中的術語"TransactionId"和google dapper中的術語"TraceId"有相同的含義。而Pinpoint中的術語"TraceId"引用到多個key的集合。

TraceId如何工做

下圖描述TraceId的行爲,在4個節點之間執行了3次的RPC調用:

圖2: TraceId行爲示例

在圖2中,TransactionId (TxId) 體現了三次不一樣的RPC做爲單個事務被相互關聯。可是,TransactionId 自己不能精確描述PRC之間的關係。爲了識別PRC之間的關係,須要SpanId 和 ParentSpanId (pSpanId). 假設一個節點是Tomcat,能夠將SpanId想象爲處理HTTP請求的線程,ParentSpanId表明發起這個RPC調用的SpanId.

使用TransactionId,Pinpoint能夠發現關聯的n個Span,並使用SpanId和ParentSpanId將這n個span排列爲繼承樹結構。

SpanId 和 ParentSpanId 是 64位長度的整型。可能發生衝突,由於這個數字是任意生成的,可是考慮到值的範圍能夠從-9223372036854775808到9223372036854775807,不太可能發生衝突. 若是key之間出現衝突,Pinpoint和Google Dapper系統,會讓開發人員知道發生了什麼,而不是解決衝突。

TransactionId 由 AgentIds, JVM (java虛擬機)啓動時間, 和 SequenceNumbers/序列號組成.

  • AgentId: 當Jvm啓動時用戶建立的ID; 必須在pinpoinit安裝的所有服務器集羣中全局惟一. 最簡單的讓它保持惟一的方法是使用hostname($HOSTNAME),由於hostname通常不會重複. 若是須要在服務器集羣中運行多個JVM,請在hostname前面增長一個前綴來避免重複。
  • JVM 啓動時間: 須要用來保證從0開始的SequenceNumber的惟一性. 當用戶錯誤的建立了重複的AgentId時這個值能夠用來預防ID衝突。
  • SequenceNumber: Pinpoint agent 生成的ID, 從0開始連續自增;爲每一個消息生成一個.

Dapper 和 Zipkin, Twitter的一個分佈式系統跟蹤平臺, 生成隨機TraceIds (Pinpoint是TransactionIds) 並將衝突狀況視爲正常。然而, 在Pinpiont中咱們想避免衝突的可能,所以實現了上面描述的系統。有兩種選擇:一是數據量小可是衝突的可能性高,二是數據量大可是衝突的可能性低。咱們選擇了第二種。

可能有更好的方式來處理transaction。咱們起先有一個想法,經過中央key服務器來生成key。若是實現這個模式,可能致使性能問題和網絡錯誤。所以,大量生成key被考慮做爲備選。後面這個方法可能被開發。如今採用簡單方法。在pinpoint中,TransactionId被當成可變數據來對待。

字節碼加強,無需代碼修改

前面咱們解釋了分佈式事務跟蹤。實現的方法之一是開發人員本身修改代碼。當發生RPC調用時允許開發人員添加標籤信息。可是,修改代碼會成爲包袱,即便這樣的功能對開發人員很是有用。

Twitter的 Zipkin 使用修改過的類庫和它本身的容器(Finagle)來提供分佈式事務跟蹤的功能。可是,它要求在須要時修改代碼。咱們指望功能能夠不修改代碼就工做並但願獲得代碼級別的可見性。爲了解決這個問題,pinpoint中使用了字節碼加強技術。Pinpoint agent干預發起RPC的代碼以此來自動處理標籤信息。

克服字節碼加強的缺點

字節碼加強術語在手工和自動方法之間的自動方法(邏輯不通啊。。。)

  • 手工方法: 開發人員使用ponpoint提供的API在關鍵點開發記錄數據的代碼
  • 自動方法: 開發人員不須要代碼改動,由於pinpoint決定了哪些API要調節和開發

下面是每一個方法的優勢和缺點:

Table1 每一個方法的優缺點

  優勢 缺點
手工跟蹤 1. 要求更少開發資源 2. API能夠更簡單並最終減小bug的數量 1. 開發人員必須修改代碼 2. 跟蹤級別低
自動跟蹤 1. 開發人員不須要修改代碼 2. 能夠收集到更多精確的數據由於有字節碼中的更多信息 1. 在開發pinpoint時,和實現一個手工方法相比,須要10倍開銷來實現一個自動方法 2. 須要更高能力的開發人員,能夠當即識別須要跟蹤的類庫代碼並決定跟蹤點 3. 增長bug發生的可能性,由於使用瞭如字節碼加強這樣的高級開發技巧

字節碼加強是一種高難度和高風險的技術。可是,使用這種技術有不少好處,考慮開發資源和難度級別。(補充:這段話翻譯的好難受。。。)

雖然它須要大量的開發資源,在開發服務上它須要不多的資源。例如,下面展現了使用字節碼加強的自動方法和使用類庫的手工方法(在這裏的上下文中,開銷是爲澄清而假設的隨機數)之間的開銷。

  • 自動方法: 總共 100

    • Pinpoint開發開銷: 100
    • 服務實施的開銷: 0
  • 手工方法: 總共 30

    • Pinpoint開發開銷: 20
    • 服務實施的開銷: 10

上面的數據告訴咱們手工方法比自動方法有更合算。可是,不適用於咱們的在NAVER的環境。在NAVER咱們有幾千個服務,所以在上面的數據中須要修改用於服務實施的開銷。若是咱們有10個服務須要修改,總開銷計算以下:

Pinpoint開發開銷 20 + 服務實施開銷 10 x 10 = 120

基於這個結果,自動方法是一個更合算的方式。

咱們很幸運的在pinpoint團隊中擁有不少高能力而專一於Java的開發人員。所以,咱們相信克服pinpoint開發中的技術難題只是個時間問題。

字節碼加強的價值

咱們選擇字節碼加強的理由,除了前面描述的那些外,還有下面的強有力的觀點:

隱藏API

一旦API被暴露給開發人員使用,咱們做爲API的提供者,就不能隨意的修改API。這樣的限制會給咱們增長壓力。

咱們可能修改API來糾正錯誤設計或者添加新的功能。可是,若是作這些受到限制,對咱們來講很難改進API。解決這個問題的最好的答案是一個可升級的系統設計,而每一個人都知道這不是一個容易的選擇。若是咱們不能掌控將來,就不可能建立完美的API設計。

而使用字節碼加強技術,咱們就沒必要擔憂暴露跟蹤API而能夠持續改進設計,不用考慮依賴關係。對於那些計劃使用pinpoint開發應用的人,換一句話說,這表明對於pinpoint開發人員,API是可變的。如今,咱們將保留隱藏API的想法,由於改進性能和設計是咱們的第一優先級。

容易啓用或者禁用

使用字節碼加強的缺點是當Pinpoint自身類庫的採樣代碼出現問題時可能影響應用。不過,能夠經過啓用或者禁用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發生問題,只須要在JVM啓動腳本中刪除這些配置數據。

字節碼如何工做

因爲字節碼加強技術處理java字節碼, 有增長開發風險的趨勢,同時會下降效率。另外,開發人員更容易犯錯。在pinpoint,咱們經過抽象出攔截器(interceptor)來改進效率和可達性(accessibility)。pinpoint在類裝載時經過介入應用代碼爲分佈式事務和性能信息注入必要的跟蹤代碼。這會提高性能,由於代碼注入是在應用代碼中直接實施的。

圖3: 字節碼加強行爲

在pinpoint中,攔截器API在性能數據被記錄的地方分開(separated)。爲了跟蹤,咱們添加攔截器到目標方法使得before()方法和after()方法被調用,並在before()方法和after()方法中實現了部分性能數據的記錄。使用字節碼加強,pinpoint agent能夠記錄須要方法的數據,只有這樣採樣數據的大小才能變小。

pinpoint agent的性能優化

最後,咱們描述用於pinpoint agent的性能優化的方式。

使用二進制格式(thrift)

經過使用二進制格式(thrift)能夠提升編碼速度,雖然它使用和調試要難一些。也有利於減小網絡使用,由於生成的數據比較小。

使用變長編碼和格式優化數據記錄

若是將一個長整型轉換爲固定長度的字符串, 數據大小通常是8個字節。然而,若是你用變長編碼,數據大小能夠是從1到10個字符,取決於給定數字的大小。爲了減少數據大小,pinpoint使用thrift的CompactProtocol協議(壓縮協議)來編碼數據,由於變長字符串和記錄數據能夠爲編碼格式作優化。pinpoint agent經過基於跟蹤的根方法的時間開始來轉換其餘的時間來減小數據大小。

圖4 說明了上面章節描述的想法:

圖4: 固定長度編碼和可變長度編碼的對比

爲了獲得關於三個不一樣方法(見圖4)被調用時間的數據,不得不在6個不一樣的點上測量時間,用固定長度編碼這須要48個字節(6 * 8)。

以此同時,pinpoint agent 使用可變長度編碼並根據對應的格式記錄數據。而後在其餘時間點經過和參考點比較來計算時間值(在vector中),根方法的起點被確認爲參考點。這隻須要佔用少許的字節,由於vector使用小數字。圖4中消耗了13個字節。

若是執行方法花費了更多時間,即便使用可變長度編碼也會增長字節數量。可是,依然比固定長度編碼更有效率。

用常量表替換重複的API信息,SQL語句和字符串

咱們但願pinpoint能開啓代碼級別的跟蹤。然而,存在增大數據大小的問題。每次高精度的數據被髮送到服務器將增大數據大小,致使增長網絡使用。

爲了解決這個問題,咱們使用了在遠程HBase中建立常量表的策略。例如,每次調用"Method A"的信息被髮送到pinpoint collector, 數據大小將很大。pinpoint agent 用一個ID替換"method A",在HBase中做爲一個常量表保存ID和"method A"的信息,而後用ID生成跟蹤數據。而後當用戶在網站上獲取跟蹤數據時,pinpoint web在常量表中搜索對應ID的方法信息並組合他們。使用一樣的方式來減小SQL或者頻繁使用的字符串的數據大小。

處理大量請求的採樣

咱們在線門戶服務有海量請求。單個服務天天處理超過200億請求。容易跟蹤這樣的請求:方法是添加足夠多的網絡設施和服務器來跟蹤請求並擴展服務器來收集數據。而後,這不是處理這種場景的合算的方法,僅僅是浪費金錢和資源。

在Pinpoint,能夠收集採樣資料而沒必要跟蹤每一個請求。在開發環境中請求量很小,每一個數據都收集。而在產品環境請求量巨大,收集小比率的數據如1~5%,足夠檢查整個應用的狀態。有采樣後,能夠最小化應用的網絡開銷並下降諸如網絡和服務器的設施費用。

pinpoint採樣方法

Pinpoint 支持計數採樣,若是設置爲10則只採樣10分之一的請求。咱們計劃增長新的採樣器來更有效率的收集數據。

注:對應的配置項在agent下的pinpoint.config文件中,默認"profiler.sampling.rate=1"表示所有

使用異步數據傳輸來最小化應用線程停止

pinpoint不阻塞應用線程,由於編碼後的數據或者遠程消息被其餘線程異步傳輸。

使用UDP傳輸數據

和gogole dapper不一樣,pinpoint經過網絡傳輸數據來確保數據速度。做爲一個服務間使用的通用設施,當數據通信持續突發時網絡會成爲問題。在這種狀況下,pinpoint agent使用UDP協議來給服務讓出網絡鏈接優先級。

注意

數據傳輸API能夠被替換,由於它是接口分離的。能夠修改實現爲用其餘方式存儲數據,好比本地文件。

pinpoint應用示例

這裏給出一個例子關於如何從應用獲取數據,這樣就能夠全面的理解前面講述的內容。

圖5 展現了當在 TomcatA 和 TomcatB 中安裝pinpoint的數據。能夠把單個節點的跟蹤數據當作single traction,提現分佈式事務跟蹤的流程。

圖5:示例1:pinpoint應用

下面闡述pinpoint爲每一個方法作了什麼:

  1. 當請求到達TomcatA時, Pinpoint agent 產生一個 TraceId.

    • TX_ID: TomcatA^TIME^1
    • SpanId: 10
    • ParentSpanId: -1(Root)
  2. 從spring MVC 控制器中記錄數據

  3. 插入HttpClient.execute()方法的調用並在HTTPGet中配置TraceId

    • 建立一個子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")
  4. 傳輸打好tag的請求到TomcatB.

    • TomcatB 檢查傳輸過來的請求的header

      HttpServletRequest.getHeader(PINPOINT_TX_ID)

    • TomcatB 做爲子節點工做由於它識別了header中的TraceId

      • TX_ID: TomcatA^TIME^1
      • SPAN_ID: 20
      • PARENT_SPAN_ID: 10
  5. 從spring mvc控制器中記錄數據並完成請求

    圖6 示例2:pinpoint應用

  6. 當從tomcatB回來的請求完成時,pinpoint agent發送跟蹤數據到pinpoint collector就此存儲在HBase中

  7. 在對tomcatB的HTTP調用結束後,TomcatA的請求也完成了。pinpoint agent發送跟蹤數據到pinpoint collector就此存儲在HBase中
  8. UI從HBase中讀取跟蹤數據並經過排序樹來建立調用棧

結論

pinpoint是和應用一塊兒運行的另外的應用。使用字節碼加強使得pinpoint看上去不須要代碼修改。一般,字節碼加強技術讓應用容易形成風險。若是問題發生在pinpoint中,它會影響應用。目前,咱們專一於改進pinpoint的性能和設計,而不是移除這樣的威脅,由於咱們任務這些讓pinpoint更加有價值。所以你須要決定是否使用pinpoint。

相關文章
相關標籤/搜索