打造立體化監控體系與APM最佳實踐系列
一、背景
年來在雲端計算、大資料等快速發展的時代下,產生了很多新的業務場景,同時很多企業傳統業務開始向網際網路的轉移。隨著企業業務的發展,規模擴大,業務越來越多,所採用的元件也越來越多開始走向分散式化,如微服務、訊息收發、分散式資料庫、分散式物件儲存、分散式快取、跨域呼叫等,這些元件共同構成了繁雜的分散式網路,一個業務請求可能會涉及到幾個、幾十個服務的協同處理,如何動態展示服務的鏈路?如何分析服務鏈路的瓶頸並對其進行調優?如何快速進行服務鏈路的故障發現?如何保障產品服務的使用者體驗?企業需要一個從程式碼端的視角來監控自己的應用進而確保自身的IT支撐系統得到高效的執行,同時需要一個強大的IT運維管理體系時刻監督IT環境各元件的效能質量,通過多維度實時分析異常並進行診斷以解決產品的問題。因此,應用效能管理(APM)將逐漸成為推廣中國IT技術進步與使用者體驗提升的標配。
當前大的網際網路公司都有自己的分散式跟蹤系統,比如Google的Dapper,Twitter的zipkin,Naver的pinpoint,淘寶的鷹眼,新浪的Watchman,京東的Hydra等,本文主要介紹zipkin的設計、安裝部署,並以一個簡單的案例演示zipkin的使用方法。
二、概述
zipkin是一款開源的分散式實時資料追蹤系統(Distributed Tracking System),基於 Google Dapper的論文設計而來,由 Twitter 公司開發貢獻。其主要功能是聚集來自各個異構系統的實時監控資料。
三、使用場景
3.1 故障快速定位
通過分析呼叫鏈,可以將一次請求的邏輯軌跡完整清晰的展示出來,通過在開發中在業務日誌中新增呼叫鏈ID,可以通過呼叫鏈結合業務日誌快速定位錯誤資訊。
3.2 效能分析
在呼叫鏈的各個環節分別新增呼叫時延,可以分析系統的效能瓶頸,進行有針對性的優化。
3.3 服務可用性
通過分析各個環節的平均時延,QPS等資訊,可以找到系統的薄弱環節,對一些模組做調整,例如資料冗餘、鏈路可用等。
四、zipkin架構
4.1 架構
如上圖所示,zipkin主要包括四個模組
Ø Collector接收各service傳輸的資料
Ø Storage儲存收集過來的資料,當前支援Cassandra,Redis,HBase,MySQL,PostgreSQL, SQLite等,預設儲存在記憶體中。
Ø API(Query)負責查詢Storage中儲存的資料,提供簡單的JSON API獲取資料,主要提供給web UI使用
Ø Web 提供簡單的web介面
各個異構的服務向zipkin報告資料的架構如下圖:
上圖中的S表示傳送跟蹤資料的客戶端SDK或者Scribe客戶端(twitter內部採用scirbe來採集跟蹤資料)。
4.2 Span
Zipkin 以 Trace 結構表示對一次請求的追蹤,又把每個 Trace 拆分為若干個有依賴關係的 Span。在微服務架構中,一次使用者請求可能會由後臺若干個服務負責處理,那麼每個處理請求的服務就可以理解為一個 Span(可以包括 API 服務,快取服務,資料庫服務以及報表服務等)。當然這個服務也可能繼續請求其他的服務,因此 Span 是一個樹形結構,以體現服務之間的呼叫關係。
Zipkin的Span模型幾乎完全仿造了Dapper中Span模型的設計,我們知道,Span用來描述一次RPC呼叫,所以一個RPC呼叫只應該關聯一個spanId,Zipkin中的Span主要包含三個資料部分:
Ø 基礎資料,包括traceId、spanId、parentId、name、timestamp和duration,主要用於跟蹤樹中節點的關聯和介面展示。
u traceId:全域性跟蹤ID,用它來標記一次完整服務呼叫,所以和一次服務呼叫相關的span中的traceId都是相同的,Zipkin將具有相同traceId的span組裝成跟蹤樹來直觀的將呼叫鏈路圖展現在我們面前。
u spanid:span的id,理論上來說,span的id只要做到一個traceId下唯一就可以。
u parentId:父span的id,呼叫有層級關係,所以span作為呼叫節點的儲存結構,也有層級關係,跟蹤鏈是採用跟蹤樹的形式來展現的,樹的根節點就是呼叫的頂點,其中parentId為null的Span將成為跟蹤樹的根節點來展示,當然它也是呼叫鏈的起點。
u name:span的名稱,主要用於在介面上展示,一般是介面方法名,name的作用是讓人知道它是哪裡採集的span。
u timestamp:span建立時的時間戳,用來記錄採集的時刻。
u duration:持續時間,即span的建立到span完成最終的採集所經歷的時間,除去span自己邏輯處理的時間,該時間段可以理解成對於該跟蹤埋點來說服務呼叫的總耗時,timestamp+duration將表示成呼叫的結束時間。
Ø Annotation,註解,用來記錄請求特定事件相關資訊(例如時間),通常包含四個註解資訊
cs – Client Start,表示客戶端發起請求
sr – Server Receive,表示服務端收到請求
ss – Server Send,表示服務端完成處理,並將結果傳送給客戶端
cr – Client Received,表示客戶端獲取到服務端返回資訊
Ø BinaryAnnotation,提供一些額外資訊,一般以key-value對出現。
如下圖是一個呼叫鏈路示例:
在本文後續章節中將會對該例項進行詳細介紹。
4.3 客戶端SDK
在上一節中我們知道對於一個APM來說,提供多種型別的客戶端SDK(instrument)是很重要的,支援的客戶端SDK越多,推廣起來也越方便,使用人群也會越多。
跟蹤資訊是使用instrument庫進行收集併發送給zipkin,截止目前,zipkin官方支援的客戶端SDK如下:
除了官方庫之後,社群也提供了instrument支援,社群支援庫如下:
五、zipkin部署
zipkin支援兩種方式部署,docker容器以及jar包執行,
docker容器方式執行命令如下:
jar包直接執行命令如下(要求java8及以上版本):
5.2 服務開發
本文中以java語言開發一個簡單demo來演示zipkin的使用,Brave 是用來裝備 Java 程式的類庫,提供了面向 Standard Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等介面的裝備能力,可以通過編寫簡單的配置和程式碼,讓基於這些框架構建的應用可以向 Zipkin 報告資料。同時 Brave 也提供了非常簡單且標準化的介面,在以上封裝無法滿足要求的時候可以方便擴充套件與定製。
服務呼叫關係如下:
新建名為service1、service2、service3、service4四個spring boot型別的專案,下面以service1專案來描述專案詳細配置,其他專案的配置類似,在此不一一詳述。
pom.xml新增如下依賴:
application.properties中增加如下配置:
配置檔案中指定了本服務的服務名以及服務埠,zipkin服務的地址。
服務定義:
當請求該服務(請求/start時),服務會請求下級的localhost:9090/foo服務-對應service2服務。
UI 啟動後主介面如下:
啟動服務service1、service2、service3、service4,並通過瀏覽器請求服務service1。
zipkin頁面檢視服務
服務呼叫鏈如下:
檢視服務依賴關係:
完整的服務跟蹤鏈資訊如下:
[
{
“traceId”: “122ecddc1769c0da”,
“id”: “122ecddc1769c0da”,
“name”: “get”,
“timestamp”: 1494383123630139,
“duration”: 2832405,
“annotations”: [
{
“timestamp”: 1494383123630139,
“value”: “sr”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383126462544,
“value”: “ss”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
}
],
“binaryAnnotations”: [
{
“key”: “http.status_code”,
“value”: “200”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “/start”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
}
]
},
{
“traceId”: “122ecddc1769c0da”,
“id”: “d92eb6cbae9b4787”,
“name”: “get”,
“parentId”: “122ecddc1769c0da”,
“timestamp”: 1494383123974246,
“duration”: 2475470,
“annotations”: [
{
“timestamp”: 1494383123974246,
“value”: “cs”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383124351184,
“value”: “sr”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383126443649,
“value”: “ss”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383126449716,
“value”: “cr”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
}
],
“binaryAnnotations”: [
{
“key”: “http.status_code”,
“value”: “200”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “/foo”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “http://localhost:9090/foo”,
“endpoint”: {
“serviceName”: “service1”,
“ipv4”: “192.168.1.10”
}
}
]
},
{
“traceId”: “122ecddc1769c0da”,
“id”: “eba2687430a3f56c”,
“name”: “get”,
“parentId”: “d92eb6cbae9b4787”,
“timestamp”: 1494383124477917,
“duration”: 558367,
“annotations”: [
{
“timestamp”: 1494383124477917,
“value”: “cs”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383124881960,
“value”: “sr”,
“endpoint”: {
“serviceName”: “service3”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383125033410,
“value”: “ss”,
“endpoint”: {
“serviceName”: “service3”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383125036284,
“value”: “cr”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
}
],
“binaryAnnotations”: [
{
“key”: “http.status_code”,
“value”: “200”,
“endpoint”: {
“serviceName”: “service3”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “http://localhost:9091/bar”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “/bar”,
“endpoint”: {
“serviceName”: “service3”,
“ipv4”: “192.168.1.10”
}
}
]
},
{
“traceId”: “122ecddc1769c0da”,
“id”: “3b0df0a2f1ea18b2”,
“name”: “get”,
“parentId”: “d92eb6cbae9b4787”,
“timestamp”: 1494383125298903,
“duration”: 1117321,
“annotations”: [
{
“timestamp”: 1494383125298903,
“value”: “cs”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383125529695,
“value”: “sr”,
“endpoint”: {
“serviceName”: “service4”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383126416224,
“value”: “cr”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“timestamp”: 1494383126424903,
“value”: “ss”,
“endpoint”: {
“serviceName”: “service4”,
“ipv4”: “192.168.1.10”
}
}
],
“binaryAnnotations”: [
{
“key”: “http.status_code”,
“value”: “200”,
“endpoint”: {
“serviceName”: “service4”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “http://localhost:9092/tar”,
“endpoint”: {
“serviceName”: “service2”,
“ipv4”: “192.168.1.10”
}
},
{
“key”: “http.url”,
“value”: “/tar”,
“endpoint”: {
“serviceName”: “service4”,
“ipv4”: “192.168.1.10”
}
}
]
}
]
六、結束語
目前市面上已知APM包括zipkin、pinpoint、appdash、cat、hydra、EagleEye等,其中cat、hydra、EagleEye分別為美團、京東以及阿里內部使用的系統,有些未開源或者開源後不再更新,pinpoint由韓國的naver開源,zipkin由Twitter開源,當前zipkin以及pinpoint社群均非常活躍,版本釋出也比較頻繁,使用者較多,生態系統也較為完善。
相對pinpoint的部署來說,zipkin部署比較簡單-執行jar包即可,pinpoint主要通過Plugin來支援眾多的模組,包括tomcat、okhttpclient等中介軟體,基本不用修改原始碼和配置檔案,對於運維人員來講最為方便,zipkin通過instrument libraries來支援眾多的中介軟體,並且有Twitter等大公司的支援,但是開發時需要對Spring、web.xml之類的配置檔案做修改。
文章來自微信公眾號:雲技術實踐