《吃透微服務(wù)》- 服務(wù)追蹤之Sleuth
大家好,我是小菜。一個希望能夠成為 吹著牛X談架構(gòu) 的男人!如果你也想成為我想成為的人,不然點個關(guān)注做個伴,讓小菜不再孤單!
本文主要介紹
SpringCloud中動態(tài)鏈路追蹤 Sleuth如有需要,可以參考
如有幫助,不忘 點贊 ?
微信公眾號已開啟,小菜良記,沒關(guān)注的同學(xué)們記得關(guān)注哦!
關(guān)于微服務(wù)的概念我們在之前的兩篇文章中都已經(jīng)做出了相應(yīng)的見解,沒看過的小伙伴可以空降查看一番,不同見解歡迎后臺留言!
這么這篇我們繼續(xù)我們的主題 《吃透微服務(wù)》 ,繼續(xù)了解分布式系統(tǒng)中解決鏈路追蹤的方案 --- Sleuth。吃透 當(dāng)然可以作為一個噱頭,但也可以作為一個目標(biāo)!
微服務(wù)的好壞我們不再爭議,最近看到很多反面宣傳微服務(wù)的特性,不知道是否大數(shù)據(jù)精準(zhǔn)投喂~ ps:估計后臺人物畫像已經(jīng)標(biāo)簽為一個反微分子了。這當(dāng)然是一句玩笑話,結(jié)束玩笑,我們來為微服務(wù)的學(xué)習(xí)添磚加瓦!
微服務(wù)的關(guān)鍵在于拆分服務(wù),但是關(guān)鍵不在 微 ,而在于合適的大小。當(dāng)我們跨出拆分服務(wù)的那一刻起,我們就得對我們拆分后的服務(wù)負(fù)責(zé),這個是一個程序員應(yīng)有的擔(dān)當(dāng)!當(dāng)服務(wù)出現(xiàn)問題的時候,你是否還苦于排查,而無從定位。線上的代碼我們無法 debug,找出問題的時刻往往出現(xiàn)以下對話:

除了無奈的苦笑,只能盲目的摸索,終于有一天程序員小菜不再妥協(xié),力求解決該問題。咱們能不能搞個類似監(jiān)控的功能,當(dāng)請求進(jìn)來的時候,分配一個身份令牌的給它,當(dāng)它在微服務(wù)中游走的時候,每個服務(wù)都會留下它的痕跡,當(dāng)它出現(xiàn)問題的時候,我們只需要檢查游走到哪一個微服務(wù),相當(dāng)于畫一個行為軌跡,通過行為軌跡追蹤服務(wù)的調(diào)用問題!沾沾自喜的同時犯嘀咕了,這說起來容易,實現(xiàn)起來好像有點困難~退堂鼓即將敲響,便想到微服務(wù)中怎么可能沒有人想到該問題,于是便游走于微服務(wù)組件之中,力求找到解決問題的實現(xiàn)。終于,Sleuth 出現(xiàn)了。

Sleuth
一、認(rèn)識 Sleuth
前面做了那么多的鋪墊,就是想引出 Sleuth 這玩意。洋洋灑灑說了一堆,那就簡單用一句話描述一下它的作用:在分布式系統(tǒng)中提供追蹤解決方案。它大量借用了 Google Dapper 的設(shè)計,難道又是個換殼的玩意?
我們先來了解一下 Sleuth 中的術(shù)語和相關(guān)概念,只為了讓我們更加專業(yè),更好 吹著牛x談架構(gòu)
Trace
由一組 Trace Id 相同的 Span 串聯(lián)形成一個樹狀結(jié)構(gòu)。為了實現(xiàn)請求追蹤,當(dāng)請求到達(dá)分布式系統(tǒng)的入口端點時,只需要服務(wù)跟蹤框架為該請求創(chuàng)建一個唯一的標(biāo)識(即 Trace Id),同時在恩不是系統(tǒng)內(nèi)部流轉(zhuǎn)的時候,框架是中保持傳遞該唯一值,直到整個請求的返回。那么我們就可以使用該唯一標(biāo)識將所有的請求串聯(lián)起來,形成一條完整的請求鏈路。
Span
代表了一組基本的工作單元。為了統(tǒng)計各處理單元的延遲,當(dāng)請求到達(dá)各個服務(wù)組件的時候,也通過一個唯一標(biāo)識(SpanId)來標(biāo)記它的開始,具體過程和結(jié)束。通過 SpanId 的開始和結(jié)束時間戳,就能統(tǒng)計該 Span 的調(diào)用時間,除此之外,我們還可以獲取如事件的名稱,請求信息等元數(shù)據(jù)
Annotation
用于記錄一段時間內(nèi)的事件。內(nèi)部使用的重要注釋如下:
cs(Client Send):客戶端發(fā)出請求,開始一個請求的事件
sr(Server Received) :服務(wù)端接受到請求開始進(jìn)行處理。
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 = 請求的總時間

理解完三個概念,我們下面就直接上手認(rèn)識!
二、掌握 Sleuth
我們老樣子請出我們的微服務(wù)項目:

簡簡單單幾個服務(wù)做個自我介紹
store-gateway: 服務(wù)網(wǎng)關(guān) store-order: 訂單服務(wù) store-product: 產(chǎn)品服務(wù)
然后我們需要在父工程中引入 Sleuth 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
直接在父工程的 dependencies 中引入,這樣子每個子服務(wù)就可以不同重復(fù)引入該依賴
然后開始我們的簡易代碼模式:
store-order服務(wù)
OrderController:

ProductService:

store-product 服務(wù)
ProductController:

我們在訂單服務(wù) 中使用 Feign 遠(yuǎn)程調(diào)用 產(chǎn)品服務(wù) 中的接口,然后啟動服務(wù)調(diào)用后看控制臺打?。?/p>
注意看我圈出來的部分,有沒有發(fā)現(xiàn)了些許不同,沒錯!這一串字符中包含的信息有 服務(wù)名 / TraceId / SpanId。依次調(diào)用有一個全局的 TraceId,將調(diào)用鏈路穿起來。通過分析微服務(wù)的日志,不難看出請求的具體過程。但是有個弊端,但服務(wù)數(shù)量增多,或者日志數(shù)量增多,從日志文件中撈出某個請求的調(diào)用過程,可并非是件易事,那么有沒有一個可以全文檢索和可視化展示的插件幫助我們解決該問題?既然提出問題,小菜自然已經(jīng)找到解決問題的方法!那就是 ZipKin
三、使用 ZipKin
ZipKin 是 Twitter 開源的一個項目,它也是基于 Google Dapper 實現(xiàn)的,主要作用便是解決我們上面提到的問題:收集服務(wù)的定時數(shù)據(jù),以解決微服務(wù)架構(gòu)中的延遲問題,包括數(shù)據(jù)的收集,存儲,查找和展現(xiàn)
我們可以使用它來收集各個服務(wù)器上請求鏈路的跟蹤數(shù)據(jù),并通過它提供的 REST API 接口來輔助我們查詢跟蹤數(shù)據(jù)以實現(xiàn)對分布式系統(tǒng)的監(jiān)控程序,從而及時地發(fā)現(xiàn)系統(tǒng)中出現(xiàn)的延遲升高問題并找出系統(tǒng)性能瓶頸的根源!
我們除了面向開發(fā)的 API 接口之外, ZipKin 也提供了方便的 UI 組件來幫我們更加直觀的搜索跟蹤信息和分析請求鏈路明細(xì),比如:可以查詢某段時間內(nèi)各用戶請求的處理時間等。
聽到 UI 組件是不是感到眼前一亮,說明我們可以通過控制臺更好的管理鏈路跟蹤
不僅如此,ZipKin 還提供了可插拔式的數(shù)據(jù)存儲方式,例如:In-Memory、MySQL、Cassandra以及Elasticsearch。支持方式不算少,總有一種你喜歡的!

我們來看看 ZipKin 的基礎(chǔ)架構(gòu)圖:

可以看出 ZipKin 結(jié)構(gòu)并不復(fù)雜,有四個核心組件構(gòu)成:
Collector:收集器組件。主要用于處理外部系統(tǒng)發(fā)現(xiàn)過來的跟蹤信息,然后將這些信息轉(zhuǎn)換為 ZipKin 內(nèi)部處理的 Span 格式,以便支持后續(xù)的存儲、分析、展示等功能 Storage: 存儲組件。主要用于對處理收集器接收到的跟蹤信息,默認(rèn)會將這些信息存儲到內(nèi)存中,我們可以修改存儲策略,將其存儲到我們的其他存儲倉庫中 RestFul API:API 組件。用于提供外部訪問的接口,比如客戶端的跟蹤信息,或外接系統(tǒng)的訪問信息 WebUI:UI 組件?;?API 組件實現(xiàn)的上層應(yīng)用,通過 UI 組件用戶可以方便而直觀地查詢和分析跟蹤信息
因此 ZiPKin 使用起來有些類似我們上篇說到的 Sentinel ,它分為 服務(wù)端 和 客戶端。
客戶端就是指微服務(wù)中的應(yīng)用,在客戶端中配置服務(wù)端的 URL 地址,一旦發(fā)生服務(wù)間的調(diào)用的時候,會被配置在微服務(wù)中的 Sleuth 的監(jiān)聽器監(jiān)聽,并生相應(yīng)的 Trace 和 Span 信息發(fā)送給服務(wù)端。
1. ZipKin 服務(wù)端
服務(wù)端是一個現(xiàn)成的 SpringBoot 服務(wù),我們只需要下載 jar 包 ,便可以直接運行!下載地址
然后我們通過命令行的方式啟動即可:
java -jar zipkin-server-2.12.9-exec.jar
成功啟動后通過訪問 http://localhost:9411 可以看到以下頁面:

到這步為止,我們服務(wù)端就已經(jīng)成功安裝。然后回到我們的客戶端集成上去
2. ZipKin 客戶端
我們需要在每個服務(wù)中引入 ZipKin 的依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
然后在配置文件中進(jìn)行配置:
spring:
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin 服務(wù)端地址
discoveryClientEnabled: false #讓nacos把它當(dāng)成一個URL,而不要當(dāng)做服務(wù)名
sleuth:
sampler:
probability: 1.0 #采樣的百分比
然后我們回到項目中,啟動項目通過訪問我們上面已經(jīng)定義好的接口訪問微服務(wù),之后在 ZipKin 的UI界面進(jìn)行觀察

可以看到已經(jīng)出現(xiàn)了我們剛剛訪問微服務(wù)的請求鏈路了,點擊其中一條記錄可以查看具體的訪問路線。

通過對請求鏈路進(jìn)行追蹤,就可以確定服務(wù)的哪一個模塊更耗時,進(jìn)而可以進(jìn)行優(yōu)化或者排查 Bug。
3. ZipKin 持久化
在 ZipKin 中默認(rèn)會將鏈路跟蹤的數(shù)據(jù)保存到內(nèi)存中,但是這種方式明顯不適合于生產(chǎn)環(huán)境。因此在 ZipKin 中支持將追蹤鏈路的數(shù)據(jù)持久化到 mysql 數(shù)據(jù)庫中或 elasticsearch 中。
MySQL
首先我們需要配置數(shù)據(jù)庫信息
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);

啟動時指定 mysql 信息
java -DSTORAGE_TYPE=mysql -DMYSQL_HOST=127.0.0.1 -DMYSQL_TCP_PORT=3306 -DMYSQL_DB=zipkin -DMYSQL_USER=root -DMYSQL_PASS=root -jar zipkin-server-2.12.9-exec.jar
ElasticSearch
啟動 ES 啟動時指定 ES 信息
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ES-HOST=localhost:9200
END
到這里我們就完成了分布式系統(tǒng)中服務(wù)鏈路跟蹤的解決!那為我們解決了問題呢?
跨微服務(wù)的API調(diào)用發(fā)生異常,快速定位出問題出在哪里。 跨微服務(wù)的API調(diào)用發(fā)生性能瓶頸,迅速定位出性能瓶頸。
不要空談,不要貪懶,和小菜一起做個吹著牛X做架構(gòu)的程序猿吧~點個關(guān)注做個伴,讓小菜不再孤單。咱們下文見!

今天的你多努力一點,明天的你就能少說一句求人的話!我是小菜,一個和你一起變強(qiáng)的男人。
??微信公眾號已開啟,小菜良記,沒關(guān)注的同學(xué)們記得關(guān)注哦!
