在醫院五天,我把「鏈路追蹤」整明白了
封面圖是 凌晨 3點半起來更文的鎖屏桌面。
前言
從上周六 7 號到今天的 11 號,我都在醫院,小孩因肺炎已經住院了,我白天和晚上的時間需要照顧娃,只能在娃睡覺的時候肝文了。對了,醫院沒有寬頻和 WiFi,我用的手機開的熱點~
本篇主要內容
這篇主要是理論 + 實踐相結合。實踐部分涉及到如何把鏈路追蹤 Sleuth
+ Zipkin
加到我的 Spring Cloud 《佳必過》開源項目上。
本篇知識點:
- 鏈路追蹤基本原理
- 如何在項目中輕鬆加入鏈路追蹤中間件
- 如何使用鏈路追蹤排查問題。
一、為什麼要用鏈路追蹤?
1.1 因:拆分服務單元
微服務
架構其實是一個分散式
的架構,按照業務劃分成了多個服務單元。
由於服務單元的數量
是很多的,有可能幾千個,而且業務也會更複雜,如果出現了錯誤和異常,很難去定位。
1.2 因:邏輯複雜
比如一個請求需要調用多個服務才能完成整個業務閉環,而內部服務的程式碼邏輯和業務邏輯比較複雜,假如某個服務出現了問題,是難以快速確定那個服務出問題的。
1.3 果:快速定位
而如果我們加上了分散式鏈路追蹤
,去跟蹤一個請求有哪些服務參與其中,參與的順序是怎樣的,這樣我們就知道了每個請求的詳細經過,即使出了問題也能快速定位。
二、鏈路追蹤的核心
鏈路追蹤組件有 Twitter 的可視化鏈路追蹤組件 Zipkin
、Google 的 Dapper
、阿里的 Eagleeye
等,而 Sleuth 是 Spring Cloud 的組件。Spring Cloud Sleuth 借鑒了 Dapper 的術語。
本文主要講解 Sleuth + Zipkin 結合使用來更好地實現鏈路追蹤。
為什麼能夠進行整條鏈路的追蹤?其實就是一個 Trace ID 將 一連串的 Span 資訊連起來了。根據 Span 記錄的資訊再進行整合就可以獲取整條鏈路的資訊。下面
2.1 Span(跨度)
-
大白話:遠程調用和 Span
一對一
。 -
基本的工作單元,每次發送一個遠程調用服務就會產生一個 Span。
-
Span 是一個 64 位的唯一 ID。
-
通過計算 Span 的開始和結束時間,就可以統計每個服務調用所花費的時間。
2.2 Trace(跟蹤)
-
大白話:一個 Trace 對應多個 Span,
一對多
。 -
它由一系列 Span 組成,樹狀結構。
-
64 位唯一 ID。
-
每次客戶端訪問微服務系統的 API 介面,可能中間會調用多個微服務,每次調用都會產生一個新的 Span,而多個 Span 組成了 Trace
2.3 Annotation(註解)
鏈路追蹤系統定義了一些核心註解,用來定義一個請求的開始和結束,注意是微服務之間的請求,而不是瀏覽器或手機等設備。註解包括:
cs
– Client Sent:客戶端發送一個請求,描述了這個請求調用的Span
的開始時間。注意:這裡的客戶端指的是微服務的調用者,不是我們理解的瀏覽器或手機等客戶端。sr
– Server Received:服務端獲得請求並準備開始處理它,如果將其sr
減去cs
時間戳,即可得到網路傳輸時間。ss
– Server Sent:服務端發送響應,會記錄請求處理完成的時間,ss
時間戳減去sr
時間戳,即可得到伺服器請求的時間。cr
– Client Received:客戶端接收響應,Span 的結束時間,如果cr
的時間戳減去cs
時間戳,即可得到一次微服務調用所消耗的時間,也就是一個Span
的消耗的總時間。
2.4 鏈路追蹤原理
假定三個微服務調用的鏈路如下圖所示:Service 1
調用 Service 2
,Service 2
調用 Service 3
和 Service 4。
那麼鏈路追蹤會在每個服務調用的時候加上 Trace ID 和 Span ID。如下圖所示:
大白話解釋:
-
大家注意上面的顏色,相同顏色的代表是同一個 Span ID,說明是鏈路追蹤中的一個節點。
-
第一步:客戶端調用
Service 1
,生成一個Request
,Trace ID
和Span ID
為空,那個時候請求還沒有到Service 1
。 -
第二步:請求到達
Service 1
,記錄了 Trace ID = X,Span ID 等於 A。 -
第三步:
Service 1
發送請求給Service 2
,Span ID 等於 B,被稱作 Client Sent,即客戶端發送一個請求。 -
第四步:請求到達
Service 2
,Span ID 等於 B,Trace ID 不會改變,被稱作 Server Received,即服務端獲得請求並準備開始處理它。 -
第五步:
Service 2
開始處理這個請求,處理完之後,Trace ID 不變,Span ID = C。 -
第六步:
Service 2
開始發送這個請求給Service 3
,Trace ID 不變,Span ID = D,被稱作 Client Sent,即客戶端發送一個請求。 -
第七步:
Service 3
接收到這個請求,Span ID = D,被稱作 Server Received。 -
第八步:
Service 3
開始處理這個請求,處理完之後,Span ID = E。 -
第九步:
Service 3
開始發送響應給Service 2
,Span ID = D,被稱作 Server Sent,即服務端發送響應。 -
第十步:
Service 3
收到Service 2
的響應,Span ID = D,被稱作 Client Received,即客戶端接收響應。 -
第十一步:
Service 2
開始返回 響應給Service 1
,Span ID = B,和第三步的 Span ID 相同,被稱作 Client Received,即客戶端接收響應。 -
第十二步:
Service 1
處理完響應,Span ID = A,和第二步的 Span ID 相同。 -
第十三步:
Service 1
開始向客戶端返迴響應,Span ID = A、 -
Service 3
向 Service 4 發送請求和Service 3
類似,對應的 Span ID 是 F 和 G。可以參照上面前面的第六步到第十步。
把以上的相同顏色的步驟簡化為下面的鏈路追蹤圖:
- 第一個節點:Span ID = A,Parent ID = null,
Service 1
接收到請求。 - 第二個節點:Span ID = B,Parent ID= A,
Service 1
發送請求到Service 2
返迴響應給Service 1
的過程。 - 第三個節點:Span ID = C,Parent ID= B,
Service 2
的 中間處理過程。 - 第四個節點:Span ID = D,Parent ID= C,
Service 2
發送請求到Service 3
返迴響應給Service 2
的過程。 - 第五個節點:Span ID = E,Parent ID= D,
Service 3
的中間處理過程。 - 第六個節點:Span ID = F,Parent ID= C,
Service 3
發送請求到 Service 4 返迴響應給Service 3
的過程。 - 第七個節點:Span ID = G,Parent ID= F,Service 4 的中間處理過程。
通過 Parent ID 即可找到父節點,整個鏈路就可以進行跟蹤追溯了。
三、Spring Cloud 整合 Sleuth
大家可以參照我的 GitHub 開源項目 PassJava(佳必過)。
3.1 引入 Spring Cloud 依賴
在 passjava-common 中引入 Spring Cloud 依賴
因為我們使用的鏈路追蹤組件 Sleuth 是 Spring Cloud 的組件,所以我們需要引入 Spring Cloud 依賴。
<dependencyManagement>
<dependencies>
<!-- Spring Cloud 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.2 引入Sleuth依賴
引入鏈路追蹤組件 Sleuth 非常簡單,在 pom.xml 文件中引入 Sleuth 依賴即可。
在 passjava-common 中引入 Sleuth 依賴:
<!-- 鏈路追蹤組件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
3.3 通過日誌觀察鏈路追蹤
我們先不整合 zipkin 鏈路追蹤可視化組件,而是通過日誌的方式來查看鏈路追蹤資訊。
文件路徑:\PassJava-Platform\passjava-question\src\main\resources\application.properties
添加配置:
logging.level.org.springframework.cloud.openfeign=debug
logging.level.org.springframework.cloud.sleuth=debug
3.4 啟動微服務
啟動以下微服務:
-
passjava-gateway 服務(網關)
-
passjava-question 服務(題目中心微服務)
-
renren 服務(Admin 後台管理服務)
啟動成功後如下圖所示:
3.5 測試跟蹤請求
打開 Admin 後台,訪問題目中心->題目配置頁面,可以看到發送了下面的請求:
//localhost:8060/api/question/v1/admin/question/list?t=1605170539929&page=1&limit=10&key=
打開控制台,可以看到列印出了追蹤日誌。
說明:
- 當沒有配置 Sleuth 鏈路追蹤的時候,INFO 資訊裡面是 [passjava-question,,,],後面跟著三個空字元串。
- 當配置了 Sleuth 鏈路追蹤的時候,追蹤到的資訊是 [passjava-question,504a5360ca906016,e55ff064b3941956,false] ,第一個是 Trace ID,第二個是 Span ID。
四、Zipkin 鏈路追蹤原理
上面我們通過簡單的引入 Sleuth 組件,就可以獲取到調用鏈路,但只能通過控制台的輸出資訊來看,不太方便。
Zipkin 油然而生,一個圖形化的工具。Zipkin 是 Twitter 開源的分散式跟蹤系統,主要用來用來收集系統的時序數據,進而可以跟蹤系統的調用問題。
而且引入了 Zipkin 組件後,就不需要引入 Sleuth 組件了,因為 Zipkin 組件已經幫我們引入了。
Zipkin 的官文://zipkin.io
4.1 Zipkin 基礎架構
Zipkin 包含四大組件:
- Collection(收集器組件),主要負責收集外部系統跟蹤資訊。
- Storage(存儲組件),主要負責將收集到的跟蹤資訊進行存儲,默認存放在記憶體中,支援存儲到 MySQL 和 ElasticSearch。
- API(查詢組件),提供介面查詢跟蹤資訊,給 UI 組件用的。
- UI (可視化 Web UI 組件),可以基於服務、時間、註解來可視化查看跟蹤資訊。注意:Web UI 不需要身份驗證。
4.2 Zipkin 跟蹤流程
流程解釋:
- 第一步:用戶程式碼發起 HTTP Get 請求,請求路徑:/foo。
- 第二步:請求到到跟蹤工具後,請求被攔截,會被記錄兩項資訊:標籤和時間戳。以及HTTP Headers 裡面會增加跟蹤頭資訊。
- 第三步:將封裝好的請求傳給 HTTP 客戶端,請求中包含 X-B3-TraceID 和 X-B3-SpanId 請求頭資訊。
- 第四步:由HTTP 客戶端發送請求。
- 第五步:Http 客戶端返迴響應 200 OK 後,跟蹤工具記錄耗時時間。
- 第六步:跟蹤工具發送 200 OK 給用戶端。
- 第七步:非同步報告 Span 資訊給 Zipkin 收集器。
五、整合 Zipkin 可視化組件
5.1 啟動虛擬機並連接
vagrant up
用 Xshell 工具連接 虛擬機。
5.2 docker 安裝 zipkin 服務
- 使用以下命令開始拉取 zipkin 鏡像並啟動 zipkin 容器。
docker run -d -p 9411:9411 openzipkin/zipkin
- 命令執行完後,會執行下載操作和啟動操作。
- 使用 docker ps 命令可以看到 zipkin 容器已經啟動成功了。如下圖所示:
- 在瀏覽器窗口打開 zipkin UI
訪問服務地址://192.168.56.10:9411/zipkin。
5.3 引入 Zipkin 依賴
在公共模組引入 zipkin 依賴
<!-- 鏈路追蹤組件 Zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
因為 zipkin 包裡面已經引入了 sleuth 組件,所以可以把之前引入的 sleuth 組件刪掉。
5.4 添加 Zipkin 配置
在需要追蹤的微服務模組下添加 zipkin 配置。
# zipkin 的伺服器地址
spring.zipkin.base-url=//192.168.56.10:9411/
# 關閉服務發現,否則 Spring Cloud 會把 zipkin 的 URL 當作服務名稱。
spring.zipkin.discovery-client-enabled=false
# 設置使用 http 的方式傳輸數據,也可以用 RabbitMQ 或 Kafka。
spring.zipkin.sender.type=web
# 設置取樣率為 100 %,默認為 0.1(10%)
spring.sleuth.sampler.probability=1
5.5 測試 Zipkin 是否工作
這裡我在 passjava-member 微服務中寫了一個 API:
passjava-member 服務的 API:getMemberStudyTimeListTest,訪問路徑為/studytime/list/test/{id}。
passjava-member 服務遠程調用 passjava-study 服務的 API:getMemberStudyTimeListTest。
我用 postman 工具測試 member 服務的 API:
打開 Zipkin 工具,搜索 passjava-member 的鏈路追蹤日誌,可以看到有一條記錄,相關說明如下圖所示:
從圖中可以看到 passjava-member 微服務調用了 passjava-study 微服務,如圖中左半部分所示。
而且 passjava-study 微服務詳細的調用時間都記錄得非常清楚,如圖中右半部分所示。
時間計算:
- 請求傳輸時間:Server Start – Client Start = 2.577s-2.339s = 0.238s
- 服務端處理時間:Server Finish – Server Start = 2.863s – 2.577s = 0.286s
- 請求總耗時:Client Finish – Client Start = 2.861s – 2.339s = 0.522s
- Passjava-member 服務總耗時:3.156 s
- Passjava-study 服務總耗時:0.521s
- 由此可以看出 passjava-member 服務花費了很長時間,性能很差。
還可以用圖標的方式查看:
六、Zipkin 數據持久化
6.1 Zipkin 支援的資料庫
Zipkin 存儲數據默認是放在記憶體中的,如果 Zipkin 重啟,那麼監控數據也會丟失。如果是生成環境,數據丟失會帶來很大問題,所以需要將 Zipkin 的監控數據持久化。而 Zipkin 支援將數據存儲到以下資料庫:
- 記憶體(默認,不建議使用)
- MySQL(數據量大的話, 查詢較為緩慢,不建議使用)
- Elasticsearch(建議使用)
- Cassandra(中國使用 Cassandra 的公司較少,相關文檔也不多)
6.2 使用 Elasticsearch 作為儲存介質
- 通過 docker 的方式配置 elasticsearch 作為 zipkin 數據的存儲介質。
docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.56.10:9200 openzipkin/zipkin-dependencies
- ES 作為存儲介質的配置參數:
七、總結
本篇講解了鏈路追蹤的核心原理,以及 Sleuth + Zipkin 的組件的原理,以及將這兩款組件加到了我的開源項目《佳必過》裡面了。
寫在最後
這周真的身心俱疲,娃也是受罪,出院後,娃吃飯也不像以前那麼積極了,看到醫生那種衣服就怕,連看到照片印表機都怕了。生怕是要給他打針、吃藥、做霧化的。還未結婚生娃的抓緊時間學習吧,加油少年~
我是悟空,努力變強,變身超級賽亞人!手寫了一套 Spring Cloud 進階教程和 PMP 刷題小程式。