Zipkin 是一款開源的分布式實時數據追蹤系統,基於 Google Dapper 的論文設計而來,由 Twitter 公司開發貢獻;其主要功能是聚集來自各個異構系統的實時監控數據。本文主要介紹下 Zipkin 的基本概念。
1、Zipkin 結構
1.1、Zipkin 服務端
Zipkin 服務端由四部分組成:
Collector:收集器組件,處理從外部系統發送過來的跟蹤信息,將這些信息轉換為 Zipkin 內部的 Span 格式,以支持后續的存儲、分析、展示等功能。
Storage:存儲組件,存儲收集器接收到的跟蹤信息,默認會將這些信息存儲在內存中;可以修改存儲策略,把跟蹤信息保存到 MySQL、cassandra、elasticsearch。
API:API組件,提供 API 接口供 UI 組件調用,外接系統訪問該 API 實現定制化的監控。
UI:UI組件,基於 API 組件實現的上層應用;通過 UI 可以方便直觀地查詢和分析跟蹤信息。
1.2、Zipkin 客戶端
Brave 是 Zipkin 提供的 Java 版本的信息采集插件;提供了面向 Standard Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等框架的采集能力,可以通過簡單的配置和代碼,讓基於這些框架構建的應用向 Zipkin 報告數據。同時 Brave 也提供了非常簡單且標准化的接口,在以上封裝無法滿足要求的時候可以方便擴展與定制。Brave 主要是利用攔截器在請求前和請求后分別埋點。例如 Spingmvc 監控使用 Interceptors,Mysql 監控使用 statementInterceptors。同理 Dubbo 的監控是利用 com.alibaba.dubbo.rpc.Filter 來過濾生產者和消費者的請求。
當然 Zipkin 也提供 其他語言的客戶端:C#、Go、JavaScript、Ruby、Scala、PHP。
1.3、Transport
連接 Zipkin 服務端和客戶端的數據傳輸通道(Transport) 可以為:Http, Kafka, Scribe。
支持的客戶端及傳輸通道詳情可參考官網:https://zipkin.io/pages/tracers_instrumentation.html
2、Zipkin 傳播規范
當上游服務通過 HTTP 調用下游服務,如何將兩個服務中的所有 span 串聯起來,形成一個 trace,這就需要上游服務將 traceId 等信息傳遞給下游服務,而不能讓下游重新生成一個 traceId。Zipkin 通過 B3 傳播規范(B3 Propagation),將相關信息(如 traceId、spanId 等)通過 HTTP 請求 Header 傳遞給下游服務:
3、Zipkin 數據模型
[{ "traceId": "8cafeeba762c479e", "parentId": "c149cb2af458c95d", "id": "b5809e988d9b47f8", "kind": "CLIENT", "name": "post", "timestamp": 1623828578382618, "duration": 848226, "localEndpoint": {"serviceName": "scdemo-client"}, "remoteEndpoint": {"ipv4": "10.49.196.1", "port": 9001}, "tags": {"http.method": "POST", "http.path": "/user/getUserServer"} }, { "traceId": "8cafeeba762c479e", "parentId": "8cafeeba762c479e", "id": "c149cb2af458c95d", "kind": "CLIENT", "name": "post", "timestamp": 1623828578373868, "duration": 860200, "localEndpoint": {"serviceName": "scdemo-client"}, "tags": {"http.method": "POST", "http.path": "/user/getUserServer"} }, { "traceId": "8cafeeba762c479e", "id": "8cafeeba762c479e", "kind": "SERVER", "name": "get /user/getuser", "timestamp": 1623828578361068, "duration": 893538, "localEndpoint": {"serviceName": "scdemo-client"}, "remoteEndpoint": {"ipv6": "::1", "port": 65247}, "tags": { "http.method": "GET", "http.path": "/user/getUser", "mvc.controller.class": "UserController", "mvc.controller.method": "getUser" } }, { "traceId": "8cafeeba762c479e", "parentId": "b5809e988d9b47f8", "id": "872ba1f271335796", "kind": "CLIENT", "name": "select", "timestamp": 1623828579160625, "duration": 16508, "localEndpoint": {"serviceName": "scdemo-server"}, "remoteEndpoint": {"serviceName": "mydb", "ipv4": "10.40.94.232", "port": 3306}, "tags": {"sql.query": "select now()"} }, { "traceId": "8cafeeba762c479e", "parentId": "b5809e988d9b47f8", "id": "26f5a8fb4cfacdfc", "kind": "CLIENT", "name": "select", "timestamp": 1623828579198865, "duration": 5071, "localEndpoint": {"serviceName": "scdemo-server"}, "remoteEndpoint": {"serviceName": "mydb", "ipv4": "10.40.94.232", "port": 3306}, "tags": {"sql.query": "select version()"} }, { "traceId": "8cafeeba762c479e", "parentId": "c149cb2af458c95d", "id": "b5809e988d9b47f8", "kind": "SERVER", "name": "post /user/getuserserver", "timestamp": 1623828578523548, "duration": 709280, "localEndpoint": {"serviceName": "scdemo-server"}, "remoteEndpoint": {"ipv4": "10.49.196.1", "port": 65248}, "tags": { "http.method": "POST", "http.path": "/user/getUserServer", "mvc.controller.class": "UserController", "mvc.controller.method": "getUser" }, "shared": true }]
traceId 一次請求全局只有一個traceId。用來在海量的請求中找到同一鏈路的幾次請求。比如servlet服務器接收到用戶請求,調用dubbo服務,然后將結果返回給用戶,整條鏈路只有一個traceId。開始於用戶請求,結束於用戶收到結果。
id 即spanId,一個鏈路中每次請求都會有一個spanId。例如一次rpc,一次sql都會有一個單獨的spanId從屬於traceId。
parentId 當前 Span 的父 Span id,通過 parentId 來確定 Span 之間的依賴關系,如果沒有 parentId,表示當前 Span 為根 Span。
timestamp Span 創建時的時間戳,單位是微秒
duration 本次請求的持續時間,單位是微秒
annotations 用於記錄請求的開始和結束:
cs Clent Sent 客戶端發起請求的時間。
cr Client Receive 客戶端收到處理完請求的時間。
ss Server Receive 服務端處理完邏輯的時間。
sr Server Receive 服務端收到調用端請求的時間。
sr - cs = 請求在網絡上的耗時 ss - sr = 服務端處理請求的耗時 cr - ss = 回應在網絡上的耗時 cr - cs = 一次調用的整體耗時
4、Zipkin 工作過程
當發起一次調用時,Zipkin 的客戶端會在入口處為整條調用鏈路生成一個全局唯一的 trace id,並為這條鏈路中的每一次分布式調用生成一個 span id。span 與 span 之間可以有父子嵌套關系,代表分布式調用中的上下游關系。span 和 span 之間可以是兄弟關系,代表當前調用下的兩次子調用。一個 trace 由一組 span 組成,可以看成是由 trace 為根節點,span 為若干個子節點的一棵樹。
Zipkin 會將 trace 相關的信息在調用鏈路上傳遞,並在每個調用邊界結束時異步的把當前調用的耗時信息上報給 Zipkin Server。Zipkin Server 在收到 trace 信息后,將其存儲起來。隨后 Zipkin 的 Web UI 會通過 API 訪問的方式從存儲中將 trace 信息提取出來分析並展示。
5、Zipkin 效果展示
Zipkin 可以查看一次請求的整個鏈路的調用情況:
也可以查看各模塊之間的依賴情況: