分布式鏈路追蹤之Spring Cloud Sleuth奪命連環(huán)九問?
文末本文轉(zhuǎn)載自微信公眾號「碼猿技術(shù)專欄」,作者不才陳某。轉(zhuǎn)載本文請聯(lián)系碼猿技術(shù)專欄公眾號。
今天這篇文章陳某介紹一下鏈路追蹤相關(guān)的知識,以Spring Cloud Sleuth和zipkin這兩個組件為主,后續(xù)文章介紹另外一種。
文章的目錄如下:
為什么需要鏈路追蹤?
大型分布式微服務(wù)系統(tǒng)中,一個系統(tǒng)被拆分成N多個模塊,這些模塊負責(zé)不同的功能,組合成一套系統(tǒng),最終可以提供豐富的功能。在這種分布式架構(gòu)中,一次請求往往需要涉及到多個服務(wù),如下圖:
服務(wù)之間的調(diào)用錯綜復(fù)雜,對于維護的成本成倍增加,勢必存在以下幾個問題:
- 服務(wù)之間的依賴與被依賴的關(guān)系如何能夠清晰的看到?
- 出現(xiàn)異常時如何能夠快速定位到異常服務(wù)?
- 出現(xiàn)性能瓶頸時如何能夠迅速定位哪個服務(wù)影響的?
為了能夠在分布式架構(gòu)中快速定位問題,分布式鏈路追蹤應(yīng)運而生。將一次分布式請求還原成調(diào)用鏈路,進行日志記錄,性能監(jiān)控并將一次分布式請求的調(diào)用情況集中展示。比如各個服務(wù)節(jié)點上的耗時、請求具體到達哪臺機器上、每個服務(wù)節(jié)點的請求狀態(tài)等等。
常見的鏈路追蹤技術(shù)有哪些?
市面上有很多鏈路追蹤的項目,其中也不乏一些優(yōu)秀的,如下:
- cat:由大眾點評開源,基于Java開發(fā)的實時應(yīng)用監(jiān)控平臺,包括實時應(yīng)用監(jiān)控,業(yè)務(wù)監(jiān)控 。集成方案是通過代碼埋點的方式來實現(xiàn)監(jiān)控,比如:攔截器,過濾器等。對代碼的侵入性很大,集成成本較高,風(fēng)險較大。
- zipkin:由Twitter公司開源,開放源代碼分布式的跟蹤系統(tǒng),用于收集服務(wù)的定時數(shù)據(jù),以解決微服務(wù)架構(gòu)中的延遲問題,包括:數(shù)據(jù)的收集、存儲、查找和展現(xiàn)。該產(chǎn)品結(jié)合spring-cloud-sleuth使用較為簡單, 集成很方便, 但是功能較簡單。
- pinpoint:韓國人開源的基于字節(jié)碼注入的調(diào)用鏈分析,以及應(yīng)用監(jiān)控分析工具。特點是支持多種插件,UI功能強大,接入端無代碼侵入
- skywalking:SkyWalking是本土開源的基于字節(jié)碼注入的調(diào)用鏈分析,以及應(yīng)用監(jiān)控分析工具。特點是支持多種插件,UI功能較強,接入端無代碼侵入。目前已加入Apache孵化器。
- Sleuth:SpringCloud 提供的分布式系統(tǒng)中鏈路追蹤解決方案。很可惜的是阿里系并沒有鏈路追蹤相關(guān)的開源項目,我們可以采用Spring Cloud Sleuth+Zipkin來做鏈路追蹤的解決方案。
Spring Cloud Sleuth是什么?
Spring Cloud Sleuth實現(xiàn)了一種分布式的服務(wù)鏈路跟蹤解決方案,通過使用Sleuth可以讓我們快速定位某個服務(wù)的問題。簡單來說,Sleuth相當(dāng)于調(diào)用鏈監(jiān)控工具的客戶端,集成在各個微服務(wù)上,負責(zé)產(chǎn)生調(diào)用鏈監(jiān)控數(shù)據(jù)。
Spring Cloud Sleuth只負責(zé)產(chǎn)生監(jiān)控數(shù)據(jù),通過日志的方式展示出來,并沒有提供可視化的UI界面。
學(xué)習(xí)Sleuth之前必須了解它的幾個概念:
- Span:基本的工作單元,相當(dāng)于鏈表中的一個節(jié)點,通過一個唯一ID標(biāo)記它的開始、具體過程和結(jié)束。我們可以通過其中存儲的開始和結(jié)束的時間戳來統(tǒng)計服務(wù)調(diào)用的耗時。除此之外還可以獲取事件的名稱、請求信息等。
- Trace:一系列的Span串聯(lián)形成的一個樹狀結(jié)構(gòu),當(dāng)請求到達系統(tǒng)的入口時就會創(chuàng)建一個唯一ID(traceId),唯一標(biāo)識一條鏈路。這個traceId始終在服務(wù)之間傳遞,直到請求的返回,那么就可以使用這個traceId將整個請求串聯(lián)起來,形成一條完整的鏈路。
- Annotation:一些核心注解用來標(biāo)注微服務(wù)調(diào)用之間的事件,重要的幾個注解如下:
- cs(Client Send):客戶端發(fā)出請求,開始一個請求的生命周期
- sr(Server Received):服務(wù)端接受請求并處理;sr-cs = 網(wǎng)絡(luò)延遲 = 服務(wù)調(diào)用的時間
- ss(Server Send):服務(wù)端處理完畢準(zhǔn)備發(fā)送到客戶端;ss - sr = 服務(wù)器上的請求處理時間
- cr(Client Reveived):客戶端接受到服務(wù)端的響應(yīng),請求結(jié)束;cr - sr = 請求的總時間
Spring Cloud 如何整合Sleuth?
整合Spring Cloud Sleuth其實沒什么的難的,在這之前需要準(zhǔn)備以下三個服務(wù):
- gateway-sleuth9031:作為網(wǎng)關(guān)服務(wù)
- sleuth-product9032:商品微服務(wù)
- sleuth-order9033:訂單微服務(wù)
三個服務(wù)的調(diào)用關(guān)系如下圖:
客戶端請求網(wǎng)關(guān)發(fā)起查詢訂單的請求,網(wǎng)關(guān)路由給訂單服務(wù),訂單服務(wù)獲取訂單詳情并且調(diào)用商品服務(wù)獲取商品詳情。
添加依賴
在父模塊中添加sleuth依賴,如下:
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-sleuth</artifactId>
- </dependency>
以上只是Spring Cloud Sleuth的依賴,還有Nacos,openFeign的依賴這里就不再詳細說了,有不清楚的可以結(jié)合陳某前面幾篇文章和案例源碼補漏一下。
調(diào)整日志級別
由于sleuth并沒有UI界面,因此需要調(diào)整一下日志級別才能在控制臺看到更加詳細的鏈路信息。
在三個服務(wù)的配置文件中添加以下配置:
- ## 設(shè)置openFeign和sleuth的日志級別為debug,方便查看日志信息
- logging:
- level:
- org.springframework.cloud.openfeign: debug
- org.springframework.cloud.sleuth: debug
演示接口完善
以下接口只是為了演示造的數(shù)據(jù),并沒有整合DB。
sleuth-order9033查詢訂單詳情的接口,如下圖:
sleuth-product9032的查詢商品詳情的接口,如下圖:
gateway-sleuth9031網(wǎng)關(guān)路由配置如下:
測試
啟動上述三個服務(wù),瀏覽器直接訪問:http://localhost:9031/order/get/12
觀察控制臺日志輸出,如下圖:
日志格式中總共有四個參數(shù),含義分別如下:
- 第一個:服務(wù)名稱
- 第二個:traceId,唯一標(biāo)識一條鏈路
- 第三個:spanId,鏈路中的基本工作單元id
- 第四個:表示是否將數(shù)據(jù)輸出到其他服務(wù),true則會把信息輸出到其他可視化的服務(wù)上觀察,這里并未整合zipkin,所以是false
好了,至此整合完成了,不禁心里倒吸一口涼氣,直接看日志那不是眼睛要看瞎了..........
什么是ZipKin?
Zipkin 是 Twitter 的一個開源項目,它基于Google Dapper實現(xiàn),它致力于收集服務(wù)的定時數(shù)據(jù),
以解決微服務(wù)架構(gòu)中的延遲問題,包括數(shù)據(jù)的收集、存儲、查找和展現(xiàn)。
ZipKin的基礎(chǔ)架構(gòu)如下圖:
Zipkin共分為4個核心的組件,如下:
- Collector:收集器組件,它主要用于處理從外部系統(tǒng)發(fā)送過來的跟蹤信息,將這些信息轉(zhuǎn)換為Zipkin內(nèi)部處理的 Span 格式,以支持后續(xù)的存儲、分析、展示等功能。
- Storage:存儲組件,它主要對處理收集器接收到的跟蹤信息,默認會將這些信息存儲在內(nèi)存中,我們也可以修改此存儲策略,通過使用其他存儲組件將跟蹤信息存儲到數(shù)據(jù)庫中
- RESTful API:API 組件,它主要用來提供外部訪問接口。比如給客戶端展示跟蹤信息,或是外接系統(tǒng)訪問以實現(xiàn)監(jiān)控等。
- UI:基于API組件實現(xiàn)的上層應(yīng)用。通過UI組件用戶可以方便而有直觀地查詢和分析跟蹤信息
zipkin分為服務(wù)端和客戶端,服務(wù)端主要用來收集跟蹤數(shù)據(jù)并且展示,客戶端主要功能是發(fā)送給服務(wù)端,微服務(wù)的應(yīng)用也就是客戶端,這樣一旦發(fā)生調(diào)用,就會觸發(fā)監(jiān)聽器將sleuth日志數(shù)據(jù)傳輸給服務(wù)端。
zipkin服務(wù)端如何搭建?
首先需要下載服務(wù)端的jar包,地址:https://search.maven.org/artifact/io.zipkin/zipkin-server/2.23.4/jar
下載完成將會得到一個jar包,如下圖:
直接啟動這個jar,命令如下:
- java -jar zipkin-server-2.23.4-exec.jar
出現(xiàn)以下界面表示啟動完成:
此時可以訪問zipkin的UI界面,地址:http://localhost:9411,界面如下:
以上是通過下載jar的方式搭建服務(wù)端,當(dāng)然也有其他方式安裝,比如docker,自己去嘗試一下吧,陳某就不再演示了。
zipKin客戶端如何搭建?
服務(wù)端只是跟蹤數(shù)據(jù)的收集和展示,客戶端才是生成和傳輸數(shù)據(jù)的一端,下面詳細介紹一下如何搭建一個客戶端。
還是上述例子的三個微服務(wù),直接添加zipkin的依賴,如下:
- <!--鏈路追蹤 zipkin依賴,其中包含Sleuth的依賴-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-zipkin</artifactId>
- </dependency>
注意:由于spring-cloud-starter-zipkin中已經(jīng)包含了Spring Cloud Sleuth依賴,因此只需要引入上述一個依賴即可。
配置文件需要配置一下zipkin服務(wù)端的地址,配置如下:
- spring:
- cloud:
- sleuth:
- sampler:
- # 日志數(shù)據(jù)采樣百分比,默認0.1(10%),這里為了測試設(shè)置成了100%,生產(chǎn)環(huán)境只需要0.1即可
- probability: 1.0
- zipkin:
- #zipkin server的請求地址
- base-url: http://127.0.0.1:9411
- #讓nacos把它當(dāng)成一個URL,而不要當(dāng)做服務(wù)名
- discovery-client-enabled: false
上述配置完成后啟動服務(wù)即可,此時訪問:http://localhost:9031/order/get/12
調(diào)用接口之后,再次訪問zipkin的UI界面,如下圖:
可以看到剛才調(diào)用的接口已經(jīng)被監(jiān)控到了,點擊SHOW進入詳情查看,如下圖:
可以看到左邊展示了一條完整的鏈路,包括服務(wù)名稱、耗時,右邊展示服務(wù)調(diào)用的相關(guān)信息,包括開始、結(jié)束時間、請求url,請求方式.....
除了調(diào)用鏈路的相關(guān)信息,還可以清楚看到每個服務(wù)的依賴如下圖,如下圖:
zipKin的數(shù)據(jù)傳輸方式如何切換?
zipkin默認的傳輸方式是HTTP,但是這里存在一個問題,一旦傳輸過程中客戶端和服務(wù)端斷掉了,那么這條跟蹤日志信息將會丟失。
當(dāng)然zipkin還支持MQ方式的傳輸,支持消息中間件有如下幾種:
- ActiveMQ
- RabbitMQ
- Kafka
使用MQ方式傳輸不僅能夠保證消息丟失的問題,還能提高傳輸效率,生產(chǎn)中推薦MQ傳輸方式。
那么問題來了,如何切換呢?
其實方式很簡單,下面陳某以RabbitMQ為例介紹一下。
1、服務(wù)端連接RabbitMQ
運行服務(wù)端并且連接RabbitMQ,命令如下:
- java -jar zipkin-server-2.23.4-exec.jar --zipkin.collector.rabbitmq.addresses=localhost --zipkin.collector.rabbitmq.username=guest --zipkin.collector.rabbitmq.password=guest
命令分析如下:
- zipkin.collector.rabbitmq.addresses:MQ地址
- zipkin.collector.rabbitmq.username:用戶名
- zipkin.collector.rabbitmq.password:密碼
2、客戶端添加RabbitMQ
既然使用MQ傳輸,肯定是要添加對應(yīng)的依賴和配置了,添加RabbitMQ依賴如下:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-amqp</artifactId>
- </dependency>
配置MQ的地址、用戶名、密碼,配置如下:
- spring:
- rabbitmq:
- addresses: 127.0.0.1
- username: guest
- password: guest
3、配置文件中傳輸方式切換
spring.cloud.zipkin.sender.type這個配置就是用來切換傳輸方式的,取值為rabbit則表示使用rabbitMQ進行數(shù)據(jù)傳輸。
配置如下:
- spring:
- cloud:
- zipkin:
- sender:
- ## 使用rabbitMQ進行數(shù)據(jù)傳輸
- type: rabbit
注意:使用MQ傳輸,則spring.cloud.zipkin.sender.base-url可以去掉。
完整的配置如下圖:
4、測試
既然使用MQ傳輸,那么我們不啟動服務(wù)端也是能夠成功傳輸?shù)?,瀏覽器訪問:http://localhost:9031/order/get/12
此時發(fā)現(xiàn)服務(wù)并沒有報異常,在看RabbitMQ中已經(jīng)有數(shù)據(jù)傳輸過來了,存在zipkin這個隊列中,如下圖:
可以看到有消息未被消費,點進去可以看到消息內(nèi)容就是Trace、Span相關(guān)信息。
好了,我們啟動服務(wù)端,命令如下:
- java -jar zipkin-server-2.23.4-exec.jar --zipkin.collector.rabbitmq.addresses=localhost --zipkin.collector.rabbitmq.username=guest --zipkin.collector.rabbitmq.password=guest
服務(wù)端啟動后發(fā)現(xiàn)zipkin隊列中的消息瞬間被消費了,查看zipkin的UI界面發(fā)現(xiàn)已經(jīng)生成了鏈路信息,如下圖:
zipkin如何持久化?
zipkin的信息默認是存儲在內(nèi)存中,服務(wù)端一旦重啟信息將會丟失,但是zipkin提供了可插拔式的存儲。
zipkin支持以下四種存儲方式:
- 內(nèi)存:服務(wù)重啟將會失效,不推薦
- MySQL:數(shù)據(jù)量越大性能較低
- Elasticsearch:主流的解決方案,推薦使用
- Cassandra:技術(shù)太牛批,用的人少,自己選擇,不過官方推薦
今天陳某就以MySQL為例介紹一下zipkin如何持久化,Elasticsearch放在下一篇,篇幅有點長。
1、創(chuàng)建數(shù)據(jù)庫
zipkin服務(wù)端的MySQL建表SQL在源碼中的zipkin-storage/mysql-v1/src/main/resources/mysql.sql中,這份SQL文件我會放在案例源碼中。
github地址:https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql
創(chuàng)建的數(shù)據(jù)庫:zipkin(名稱任意),導(dǎo)入建表SQL,新建的數(shù)據(jù)庫表如下圖:
2、服務(wù)端配置MySQL
服務(wù)端配置很簡單,運行如下命令:
- java -jar zipkin-server-2.23.4-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=Nov2014
上述命令參數(shù)分析如下:
- STORAGE_TYPE:指定存儲的方式,默認內(nèi)存形式
- MYSQL_HOST:MySQL的ip地址,默認localhost
- MYSQL_TCP_PORT:MySQL的端口號,默認端口3306
- MYSQL_DB:MySQL中的數(shù)據(jù)庫名稱,默認是zipkin
- MYSQL_USER:用戶名
- MYSQL_PASS:密碼
陳某是如何記得這些參數(shù)的?廢話,肯定記不住,隨時查看下源碼不就得了,這些配置都在源碼的/zipkin-server/src/main/resources/zipkin-server-shared.yml這個配置文件中,比如上述MySQL的相關(guān)配置,如下圖:
zipkin服務(wù)端的所有配置項都在這里,沒事去翻翻看。
github地址:https://github.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/resources/zipkin-server-shared.yml
那么采用rabbitMQ傳輸方式、MySQL持久化方式,完整的命令如下:
- java -jar zipkin-server-2.23.4-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=Nov2014 --zipkin.collector.rabbitmq.addresses=localhost --zipkin.collector.rabbitmq.username=guest --zipkin.collector.rabbitmq.password=guest
持久化是服務(wù)端做的事,和客戶端無關(guān),因此到這就完事了,陳某就不再測試了,自己動手試試吧。
總結(jié)
前面介紹了這么多,不知道大家有沒有仔細看,陳某總結(jié)一下吧:
- Spring Cloud Sleuth 作為鏈路追蹤的一種組件,只提供了日志采集,日志打印的功能,并沒有可視化的UI界面
- zipkin提供了強大的日志追蹤分析、可視化、服務(wù)依賴分析等相關(guān)功能,結(jié)合Spring Cloud Sleuth作為一種主流的解決方案
- zipkin生產(chǎn)環(huán)境建議切換的MQ傳輸模式,這樣做有兩個優(yōu)點
- 防止數(shù)據(jù)丟失
- MQ異步解耦,性能提升很大
- zipkin默認是內(nèi)存的形式存儲,MySQL雖然也是一種方式,但是隨著數(shù)據(jù)量越大,性能越差,因此生產(chǎn)環(huán)境建議采用Elasticsearch,下一篇文章介紹。