1. 程式人生 > >Spring Cloud 系列之 Sleuth 鏈路追蹤(一)

Spring Cloud 系列之 Sleuth 鏈路追蹤(一)

  隨著微服務架構的流行,服務按照不同的維度進行拆分,一次請求往往需要涉及到多個服務。網際網路應用構建在不同的軟體模組集上,這些軟體模組,有可能是由不同的團隊開發、可能使用不同的程式語言來實現、有可能布在了幾千臺伺服器,橫跨多個不同的資料中心。因此,就需要一些可以幫助理解系統行為、用於分析效能問題的工具,以便發生故障的時候,能夠快速定位和解決問題。在複雜的微服務架構系統中,幾乎每一個前端請求都會形成一個複雜的分散式服務呼叫鏈路。一個請求完整呼叫鏈可能如下圖所示: ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/2062729-8d66b8b261c9c6b4.jpg)   隨著服務的越來越多,對呼叫鏈的分析會越來越複雜。它們之間的呼叫關係也許如下: ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/2279594-4b7d1b6abe595390.png)   隨著業務規模不斷增大、服務不斷增多以及頻繁變更的情況下,面對複雜的呼叫鏈路就帶來一系列問題: - 如何快速發現問題? - 如何判斷故障影響範圍? - 如何梳理服務依賴以及依賴的合理性? - 如何分析鏈路效能問題以及實時容量規劃?      而鏈路追蹤的出現正是為了解決這種問題,它可以在複雜的服務呼叫中定位問題,還可以在新人加入後臺團隊之後,讓其清楚地知道自己所負責的服務在哪一環。   除此之外,如果某個介面突然耗時增加,也不必再逐個服務查詢耗時情況,我們可以直觀地分析出服務的效能瓶頸,方便在流量激增的情況下精準合理地擴容。    ## 什麼是鏈路追蹤      “鏈路追蹤”一詞是在 2010 年提出的,當時谷歌釋出了一篇 Dapper 論文:[Dapper,大規模分散式系統的跟蹤系統](http://bigbully.github.io/Dapper-translation/),介紹了谷歌自研的分散式鏈路追蹤的實現原理,還介紹了他們是怎麼低成本實現對應用透明的。   **單純的理解鏈路追蹤,就是指一次任務的開始到結束,期間呼叫的所有系統及耗時(時間跨度)都可以完整記錄下來。**   其實 Dapper 一開始只是一個獨立的呼叫鏈路追蹤系統,後來逐漸演化成了監控平臺,並且基於監控平臺孕育出了很多工具,比如實時預警、過載保護、指標資料查詢等。   除了谷歌的 Dapper,還有一些其他比較有名的產品,比如阿里的鷹眼、大眾點評的 CAT、Twitter 的 Zipkin、Naver(著名社交軟體LINE的母公司)的 PinPoint 以及國產開源的 SkyWalking(已貢獻給 Apache) 等。    ## 什麼是 Sleuth      Spring Cloud Sleuth 為 Spring Cloud 實現了分散式跟蹤解決方案。相容 Zipkin,HTrace 和其他基於日誌的追蹤系統,例如 ELK(Elasticsearch 、Logstash、 Kibana)。   Spring Cloud Sleuth 提供了以下功能: - `鏈路追蹤`:通過 Sleuth 可以很清楚的看出一個請求都經過了那些服務,可以很方便的理清服務間的呼叫關係等。 - `效能分析`:通過 Sleuth 可以很方便的看出每個取樣請求的耗時,分析哪些服務呼叫比較耗時,當服務呼叫的耗時隨著請求量的增大而增大時, 可以對服務的擴容提供一定的提醒。 - `資料分析,優化鏈路`:對於頻繁呼叫一個服務,或並行呼叫等,可以針對業務做一些優化措施。 - `視覺化錯誤`:對於程式未捕獲的異常,可以配合 Zipkin 檢視。    ## 專業術語      點選連結觀看:Sleuth 專業術語視訊(獲取更多請關注公眾號「哈嘍沃德先生」)    ### Span      基本工作單位,一次單獨的呼叫鏈可以稱為一個 Span,Dapper 記錄的是 Span 的名稱,以及每個 Span 的 ID 和父 ID,以重建在一次追蹤過程中不同 Span 之間的關係,圖中一個矩形框就是一個 Span,前端從發出請求到收到回覆就是一個 Span。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/img2.png) > 開始跟蹤的初始跨度稱為`root span`。該跨度的 ID 的值等於跟蹤 ID。   Dapper 記錄了 span 名稱,以及每個 span 的 ID 和父 span ID,以重建在一次追蹤過程中不同 span 之間的關係。如果一個 span 沒有父 ID 被稱為 root span。所有 span 都掛在一個特定的 Trace 上,也共用一個 trace id。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/img3.png)    ### Trace      一系列 Span 組成的樹狀結構,一個 Trace 認為是一次完整的鏈路,內部包含 n 多個 Span。Trace 和 Span 存在一對多的關係,Span 與 Span 之間存在父子關係。   舉個例子:客戶端呼叫服務 A 、服務 B 、服務 C 、服務 F,而每個服務例如 C 就是一個 Span,如果在服務 C 中另起執行緒呼叫了 D,那麼 D 就是 C 的子 Span,如果在服務 D 中另起執行緒呼叫了 E,那麼 E 就是 D 的子 Span,這個 C -> D -> E 的鏈路就是一條 Trace。如果鏈路追蹤系統做好了,鏈路資料有了,藉助前端解析和渲染工具,可以達到下圖中的效果: ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1569484-20190414200750593-370575235.png) ### Annotation      用來及時記錄一個事件的存在,一些核心 annotations 用來定義一個請求的開始和結束。 - cs - Client Sent:客戶端發起一個請求,這個 annotation 描述了這個 span 的開始; - sr - Server Received:服務端獲得請求並準備開始處理它,如果 sr 減去 cs 時間戳便可得到網路延遲; - ss - Server Sent:請求處理完成(當請求返回客戶端),如果 ss 減去 sr 時間戳便可得到服務端處理請求需要的時間; - cr - Client Received:表示 span 結束,客戶端成功接收到服務端的回覆,如果 cr 減去 cs 時間戳便可得到客戶端從服務端獲取回覆的所有所需時間。    ## 實現原理      首先感謝張以諾製作的實現原理圖。   如果想知道一個介面在哪個環節出現了問題,就必須清楚該介面呼叫了哪些服務,以及呼叫的順序,如果把這些服務串起來,看起來就像鏈條一樣,我們稱其為呼叫鏈。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231653342-811888696.png)   想要實現呼叫鏈,就要為每次呼叫做個標識,然後將服務按標識大小排列,可以更清晰地看出呼叫順序,我們暫且將該標識命名為 spanid。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231705602-252587237.png)   實際場景中,我們需要知道某次請求呼叫的情況,所以只有 spanid 還不夠,得為每次請求做個唯一標識,這樣才能根據標識查出本次請求呼叫的所有服務,而這個標識我們命名為 traceid。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231719802-1069090036.png)   現在根據 spanid 可以輕易地知道被呼叫服務的先後順序,但無法體現呼叫的層級關係,正如下圖所示,多個服務可能是逐級呼叫的鏈條,也可能是同時被同一個服務呼叫。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231728198-1530247525.png)   所以應該每次都記錄下是誰呼叫的,我們用 parentid 作為這個標識的名字。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231736945-875872485.png)   到現在,已經知道呼叫順序和層級關係了,但是接口出現問題後,還是不能找到出問題的環節,如果某個服務有問題,那個被呼叫執行的服務一定耗時很長,要想計算出耗時,上述的三個標識還不夠,還需要加上時間戳,時間戳可以更精細一點,精確到微秒級。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231746507-1356982492.png)   只記錄發起呼叫時的時間戳還算不出耗時,要記錄下服務返回時的時間戳,有始有終才能算出時間差,既然返回的也記了,就把上述的三個標識都記一下吧,不然區分不出是誰的時間戳。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231756180-1551132294.png)   雖然能計算出從服務呼叫到服務返回的總耗時,但是這個時間包含了服務的執行時間和網路延遲,有時候我們需要區分出這兩類時間以方便做針對性優化。那如何計算網路延遲呢?我們可以把呼叫和返回的過程分為以下四個事件。 - Client Sent 簡稱 cs,客戶端發起呼叫請求到服務端。 - Server Received 簡稱 sr,指服務端接收到了客戶端的呼叫請求。 - Server Sent 簡稱 ss,指服務端完成了處理,準備將資訊返給客戶端。 - Client Received 簡稱 cr,指客戶端接收到了服務端的返回資訊。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231805012-1235793153.png)   假如在這四個事件發生時記錄下時間戳,就可以輕鬆計算出耗時,比如 sr 減去 cs 就是呼叫時的網路延遲,ss 減去 sr 就是服務執行時間,cr 減去 ss 就是服務響應的延遲,cr 減 cs 就是整個服務呼叫執行的時間。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/1471773-20190614231813903-1860370592.png)   其實 span 內除了記錄這幾個引數之外,還可以記錄一些其他資訊,比如發起呼叫服務名稱、被調服務名稱、返回結果、IP、呼叫服務的名稱等,最後,我們再把相同 parentid 的 span 資訊合成一個大的 span 塊,就完成了一個完整的呼叫鏈。    ## 環境準備      `sleuth-demo` 聚合工程。`SpringBoot 2.2.4.RELEASE`、`Spring Cloud Hoxton.SR1`。 - `eureka-server`:註冊中心 - `eureka-server02`:註冊中心 - `gateway-server`:Spring Cloud Gateway 服務閘道器 - `product-service`:商品服務,提供了根據主鍵查詢商品介面 `http://localhost:7070/product/{id}` 根據多個主鍵查詢商品介面 `http://localhost:7070/product/listByIds` - `order-service`:訂單服務,提供了根據主鍵查詢訂單介面 `http://localhost:9090/order/{id}` 且訂單服務呼叫商品服務。    ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200208132104800.png)    ## 入門案例      點選連結觀看:Sleuth 入門案例視訊(獲取更多請關注公眾號「哈嘍沃德先生」)    ### 新增依賴      在需要進行鏈路追蹤的專案中(服務閘道器、商品服務、訂單服務)新增 `spring-cloud-starter-sleuth` 依賴。 ```xml org.springframework.cloud
spring-cloud-starter-sleuth
```    ### 記錄日誌      在需要鏈路追蹤的專案中新增 `logback.xml` 日誌檔案,內容如下(logback 日誌的輸出級別需要是 DEBUG 級別):   注意修改 `` 中專案名稱。   日誌核心配置:`%d{yyyy-MM-dd HH:mm:ss.SSS} [${applicationName},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-}] [%thread] %-5level %logger{50} - %msg%n` ```xml my_logback
DEBUG ${LOG_PATTERN} UTF-8 ${log.path}/log_debug.log ${LOG_PATTERN} UTF-8 ${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log
100MB 15
DEBUG ACCEPT DENY ${log.path}/log_info.log ${LOG_PATTERN} UTF-8 ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 100MB 15 INFO ACCEPT DENY ${log.path}/log_warn.log ${LOG_PATTERN} UTF-8 ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 100MB 15 WARN ACCEPT DENY ${log.path}/log_error.log ${LOG_PATTERN} UTF-8 ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 100MB 15 10GB ERROR ACCEPT DENY
```    ### 訪問      訪問:http://localhost:9000/order-service/order/1 ,結果如下:   服務閘道器列印資訊: ```shell [gateway-server,95aa725089b757f8,95aa725089b757f8] ```   商品服務列印資訊 ```shell [product-service,95aa725089b757f8,e494e064842ce4e8] ```   訂單服務列印資訊 ```shell [order-service,95aa725089b757f8,f4ee41a6dcf08717] ```   通過列印資訊可以得知,整個鏈路的 `traceId` 為:`95aa725089b757f8`,`spanId` 為:`e494e064842ce4e8` 和 `f4ee41a6dcf08717`。   檢視日誌檔案並不是一個很好的方法,當微服務越來越多日誌檔案也會越來越多,查詢工作會變得越來越麻煩,Spring 官方推薦使用 Zipkin 進行鏈路跟蹤。Zipkin 可以將日誌聚合,並進行視覺化展示和全文檢索。    ## 使用 Zipkin 進行鏈路跟蹤    ### 什麼是 Zipkin    ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/zipkin_vertical_grey_gb.png)      [Zipkin](https://zipkin.io/) 是 Twitter 公司開發貢獻的一款開源的分散式實時資料追蹤系統(Distributed Tracking System),基於 Google Dapper 的論文設計而來,其主要功能是聚集各個異構系統的實時監控資料。   它可以收集各個伺服器上請求鏈路的跟蹤資料,並通過 Rest API 介面來輔助我們查詢跟蹤資料,實現對分散式系統的實時監控,及時發現系統中出現的延遲升高問題並找出系統性能瓶頸的根源。除了面向開發的 API 介面之外,它還提供了方便的 UI 元件,每個服務向 Zipkin 報告計時資料,Zipkin 會根據呼叫關係生成依賴關係圖,幫助我們直觀的搜尋跟蹤資訊和分析請求鏈路明細。Zipkin 提供了可插拔資料儲存方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。   分散式跟蹤系統還有其他比較成熟的實現,例如:Naver 的 PinPoint、Apache 的 HTrace、阿里的鷹眼 Tracing、京東的 Hydra、新浪的 Watchman,美團點評的 CAT,Apache 的 SkyWalking 等。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/v2-a32471042408c726c7c944456f8e1e34_hd.jpg)    ### 工作原理    ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/zipkin.jpg)   共有四個元件構成了 Zipkin: - `Collector`:收集器元件,處理從外部系統傳送過來的跟蹤資訊,將這些資訊轉換為 Zipkin 內部處理的 Span 格式,以支援後續的儲存、分析、展示等功能。 - `Storage`:儲存元件,處理收集器接收到的跟蹤資訊,預設將資訊儲存在記憶體中,可以修改儲存策略使用其他儲存元件,支援 MySQL,Elasticsearch 等。 - `Web UI`:UI 元件,基於 API 元件實現的上層應用,提供 Web 頁面,用來展示 Zipkin 中的呼叫鏈和系統依賴關係等。 - `RESTful API`:API 元件,為 Web 介面提供查詢儲存中資料的介面。      Zipkin 分為兩端,一個是 Zipkin 服務端,一個是 Zipkin 客戶端,客戶端也就是微服務的應用,客戶端會配置服務端的 URL 地址,一旦發生服務間的呼叫的時候,會被配置在微服務裡面的 Sleuth 的監聽器監聽,並生成相應的 Trace 和 Span 資訊傳送給服務端。傳送的方式有兩種,一種是訊息匯流排的方式如 RabbitMQ 傳送,還有一種是 HTTP 報文的方式傳送。    ### 服務端部署      服務端是一個獨立的可執行的 jar 包,官方下載地址:https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec,使用 `java -jar zipkin.jar` 命令啟動,埠預設為 `9411`。我們下載的 jar 包為:zipkin-server-2.20.1-exec.jar,啟動命令如下: ```shell java -jar zipkin-server-2.20.1-exec.jar ```      訪問:http://localhost:9411/ 結果如下:   目前最新版介面。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200311151024323.png)      之前舊版本介面。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200208183623587.png)    ### 客戶端部署      點選連結觀看:Zipkin 客戶端部署視訊(獲取更多請關注公眾號「哈嘍沃德先生」)    #### 新增依賴      在需要進行鏈路追蹤的專案中(服務閘道器、商品服務、訂單服務)新增 `spring-cloud-starter-zipkin` 依賴。 ```xml org.springframework.cloud spring-cloud-starter-zipkin ```    #### 配置檔案      在需要進行鏈路追蹤的專案中(服務閘道器、商品服務、訂單服務)配置 Zipkin 服務端地址及資料傳輸方式。預設即如下配置。 ```yml spring: zipkin: base-url: http://localhost:9411/ # 服務端地址 sender: type: web # 資料傳輸方式,web 表示以 HTTP 報文的形式向服務端傳送資料 sleuth: sampler: probability: 1.0 # 收集資料百分比,預設 0.1(10%) ```    #### 訪問      訪問:http://localhost:9000/order-service/order/1 結果如下: ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200208195235916.png)      新版操作如下:   訪問:http://localhost:9411/ 根據時間過濾點選`搜尋`結果如下: ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200311152641383.png)      點選對應的追蹤資訊可檢視請求鏈路詳細。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200311152843827.png)      通過依賴可以檢視鏈路中服務的依賴關係。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200311153040971.png)      舊版操作如下:   訪問:http://localhost:9411/ 點選`查詢`結果如下: ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200208194810973.png)      點選對應的追蹤資訊可檢視請求鏈路詳細。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200208195010412.png)      通過依賴可以檢視鏈路中服務的依賴關係。 ![](https://mrhelloworld.com/resources/articles/spring/spring-cloud/sleuth/image-20200208195125216.png)      Zipkin Server 預設儲存追蹤資料至記憶體中,這種方式並不適合生產環境,一旦 Server 關閉重啟或者服務崩潰,就會導致歷史資料消失。Zipkin 支援修改儲存策略使用其他儲存元件,支援 MySQL,Elasticsearch 等。 > 下一篇我們講解 Sleuth 基於 Zipkin 儲存鏈路追蹤資料至 MySQL,Elasticsearch 以及使用 MQ 儲存鏈路追蹤資料至 MySQL,Elasticsearch,記得關注噢~ ![](https://mrhelloworld.com/resources/articles/articles_bottom/end02.gif)   本文采用 `知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議`。   大家可以通過 `分類` 檢視更多關於 `Spring Cloud` 的文章。