1. 程式人生 > >在醫院五天,我把「鏈路追蹤」整明白了

在醫院五天,我把「鏈路追蹤」整明白了

![封面](https://img-blog.csdnimg.cn/img_convert/529165137ac22ad2f94cc6c1ae7f4702.png) 封面圖是 凌晨 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。 ![微服務呼叫鏈路圖](https://img-blog.csdnimg.cn/img_convert/5221c7a75f3363d081dff1e409116a42.png) 那麼鏈路追蹤會在每個服務呼叫的時候加上 Trace ID 和 Span ID。如下圖所示: ![鏈路追蹤原理圖](https://img-blog.csdnimg.cn/img_convert/a6b71d4080b738f3af381f7218be51e5.png) **大白話解釋:** - 大家注意上面的顏色,相同顏色的代表是同一個 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。可以參照上面前面的第六步到第十步。 **把以上的相同顏色的步驟簡化為下面的鏈路追蹤圖:** ![鏈路追蹤父子節點圖](https://img-blog.csdnimg.cn/img_convert/4b14345f5bb6cb275d698cb775265584.png) - 第一個節點: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 依賴。 ``` org.springframework.cloud spring-cloud-dependencies Hoxton.SR3 pom import ``` ### 3.2 引入Sleuth依賴 引入鏈路追蹤元件 Sleuth 非常簡單,在 pom.xml 檔案中引入 Sleuth 依賴即可。 在 passjava-common 中引入 Sleuth 依賴: ```xml org.springframework.cloud
spring-cloud-starter-sleuth
``` ### 3.3 通過日誌觀察鏈路追蹤 我們先不整合 zipkin 鏈路追蹤視覺化元件,而是通過日誌的方式來檢視鏈路追蹤資訊。 ```properties 檔案路徑:\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 後臺管理服務) 啟動成功後如下圖所示: ![啟動微服務](https://img-blog.csdnimg.cn/img_convert/8f65e8fa859ac9b450aef4b0edbd6d33.png) ### 3.5 測試跟蹤請求 開啟 Admin 後臺,訪問題目中心->
題目配置頁面,可以看到傳送了下面的請求: ```http http://localhost:8060/api/question/v1/admin/question/list?t=1605170539929&page=1&limit=10&key= ``` ![佳必過專案的後臺介面](https://img-blog.csdnimg.cn/img_convert/032cf178e2047aa7e6ec6feb1fb5e9ce.png) 開啟控制檯,可以看到打印出了追蹤日誌。 ![鏈路追蹤日誌](https://img-blog.csdnimg.cn/img_convert/5ec427cd4610aa90e8f000dd4fe6037f.png) 說明: - 當沒有配置 Sleuth 鏈路追蹤的時候,INFO 資訊裡面是 [passjava-question,,,],後面跟著三個空字串。 - 當配置了 Sleuth 鏈路追蹤的時候,追蹤到的資訊是 [passjava-question,504a5360ca906016,e55ff064b3941956,false] ,第一個是 Trace ID,第二個是 Span ID。 ## 四、Zipkin 鏈路追蹤原理 上面我們通過簡單的引入 Sleuth 元件,就可以獲取到呼叫鏈路,但只能通過控制檯的輸出資訊來看,不太方便。 Zipkin 油然而生,一個圖形化的工具。Zipkin 是 Twitter 開源的分散式跟蹤系統,主要用來用來收集系統的時序資料,進而可以跟蹤系統的呼叫問題。 而且引入了 Zipkin 元件後,就不需要引入 Sleuth 元件了,因為 Zipkin 元件已經幫我們引入了。 Zipkin 的官文:https://zipkin.io ### 4.1 Zipkin 基礎架構 ![Zipkin 基礎架構](https://img-blog.csdnimg.cn/img_convert/8c3373d8fb0fc4ea9d7fc157d876b711.png) **Zipkin 包含四大元件:** - Collection(收集器元件),主要負責收集外部系統跟蹤資訊。 - Storage(儲存元件),主要負責將收集到的跟蹤資訊進行儲存,預設存放在記憶體中,支援儲存到 MySQL 和 ElasticSearch。 - API(查詢元件),提供介面查詢跟蹤資訊,給 UI 元件用的。 - UI (視覺化 Web UI 元件),可以基於服務、時間、註解來視覺化檢視跟蹤資訊。注意:Web UI 不需要身份驗證。 ### 4.2 Zipkin 跟蹤流程 ![ Zipkin 跟蹤流程](https://img-blog.csdnimg.cn/img_convert/9381fdb3bc4ba40cee52dbd599892bf3.png) **流程解釋:** - 第一步:使用者程式碼發起 HTTP Get 請求,請求路徑:/foo。 - 第二步:請求到到跟蹤工具後,請求被攔截,會被記錄兩項資訊:標籤和時間戳。以及HTTP Headers 裡面會增加跟蹤頭資訊。 - 第三步:將封裝好的請求傳給 HTTP 客戶端,請求中包含 X-B3-TraceID 和 X-B3-SpanId 請求頭資訊。 - 第四步:由HTTP 客戶端傳送請求。 - 第五步:Http 客戶端返回響應 200 OK 後,跟蹤工具記錄耗時時間。 - 第六步:跟蹤工具傳送 200 OK 給使用者端。 - 第七步:非同步報告 Span 資訊給 Zipkin 收集器。 ## 五、整合 Zipkin 視覺化元件 ### 5.1 啟動虛擬機器並連線 ```sh vagrant up ``` ![啟動虛擬機器](https://img-blog.csdnimg.cn/img_convert/c955434095cd6cadada93b1979f66251.png) 用 Xshell 工具連線 虛擬機器。 ### 5.2 docker 安裝 zipkin 服務 - 使用以下命令開始拉取 zipkin 映象並啟動 zipkin 容器。 ```sh docker run -d -p 9411:9411 openzipkin/zipkin ``` - 命令執行完後,會執行下載操作和啟動操作。 ![docker 安裝 zipkin 服務](https://img-blog.csdnimg.cn/img_convert/c78bedbe2923e8441859f833bb3e1f16.png) - 使用 docker ps 命令可以看到 zipkin 容器已經啟動成功了。如下圖所示: ![zipkin 容器啟動成功](https://img-blog.csdnimg.cn/img_convert/a00b039963007b06486fcc40efe3ff09.png) - 在瀏覽器視窗開啟 zipkin UI 訪問服務地址:http://192.168.56.10:9411/zipkin。 ### 5.3 引入 Zipkin 依賴 在公共模組引入 zipkin 依賴 ```xml org.springframework.cloud
spring-cloud-starter-zipkin
``` 因為 zipkin 包裡面已經引入了 sleuth 元件,所以可以把之前引入的 sleuth 元件刪掉。 ### 5.4 新增 Zipkin 配置 在需要追蹤的微服務模組下新增 zipkin 配置。 ```properties # zipkin 的伺服器地址 spring.zipkin.base-url=http://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: ![測試 Passjava-member 服務的 API](https://img-blog.csdnimg.cn/img_convert/a62331af926344be7f37960d5bdd48ec.png) 開啟 Zipkin 工具,搜尋 passjava-member 的鏈路追蹤日誌,可以看到有一條記錄,相關說明如下圖所示: ![zipkin 示例](https://img-blog.csdnimg.cn/img_convert/50de30c7bb10fa6a7960e05853cec5eb.png) 從圖中可以看到 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 服務花費了很長時間,效能很差。 還可以用圖示的方式檢視: ![圖示的方式檢視](https://img-blog.csdnimg.cn/img_convert/719ab43799cb09b8f7c81ff2a2c01b93.png) ## 六、Zipkin 資料持久化 ### 6.1 Zipkin 支援的資料庫 Zipkin 儲存資料預設是放在記憶體中的,如果 Zipkin 重啟,那麼監控資料也會丟失。如果是生成環境,資料丟失會帶來很大問題,所以需要將 Zipkin 的監控資料持久化。而 Zipkin 支援將資料儲存到以下資料庫: - 記憶體(預設,不建議使用) - MySQL(資料量大的話, 查詢較為緩慢,不建議使用) - Elasticsearch(建議使用) - Cassandra(國內使用 Cassandra 的公司較少,相關文件也不多) ### 6.2 使用 Elasticsearch 作為儲存介質 - 通過 docker 的方式配置 elasticsearch 作為 zipkin 資料的儲存介質。 ```sh docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.56.10:9200 openzipkin/zipkin-dependencies ``` - ES 作為儲存介質的配置引數: ![ES 作為儲存介質的配置引數](https://img-blog.csdnimg.cn/img_convert/3ddd9b3d236dc4faee528bbc4853a677.png) ## 七、總結 本篇講解了鏈路追蹤的核心原理,以及 Sleuth + Zipkin 的元件的原理,以及將這兩款元件加到了我的開源專案《佳必過》裡面了。 > 開源專案地址:https://github.com/Jackson0714/PassJava-Platform ## 寫在最後 這周真的身心俱疲,娃也是受罪,出院後,娃吃飯也不像以前那麼積極了,看到醫生那種衣服就怕,連看到照片印表機都怕了。生怕是要給他打針、吃藥、做霧化的。還未結婚生娃的抓緊時間學習吧,加油少年~ > 我是悟空,努力變強,變身超級賽亞人!手寫了一套 Spring Cloud 進階教程和 PMP 刷題小程式。