1. 什么是調(diào)用鏈
一個(gè)業(yè)務(wù)功能可能需要多個(gè)服務(wù)協(xié)作才能實(shí)現(xiàn),一個(gè)請(qǐng)求到達(dá)服務(wù)A,服務(wù)A需要依賴服務(wù)B,服務(wù)B又依賴服務(wù)C,甚至C仍需依賴其他服務(wù),形成一個(gè)調(diào)用鏈條,即調(diào)用鏈。
2. 為什么要監(jiān)控調(diào)用鏈

上圖傳遞了一個(gè)信息,就是微服務(wù)的復(fù)雜性
- 出現(xiàn)問題后,定位困難,需要對(duì)整個(gè)調(diào)用鏈路有個(gè)完善的監(jiān)控
- 鏈路復(fù)雜,需要清晰的鏈路圖譜反映服務(wù)之間的依賴、調(diào)用關(guān)系
- 整體系統(tǒng)性能及運(yùn)行情況,需要明確的體現(xiàn),才能根據(jù)實(shí)際情況調(diào)整資源
3. 要監(jiān)控哪些方面
- 圖形化展示整個(gè)調(diào)用鏈路
- 系統(tǒng)的性能指標(biāo)
- 健康狀況
- 基礎(chǔ)告警
4. 調(diào)用鏈監(jiān)控的基礎(chǔ)原理
在介紹調(diào)用鏈監(jiān)控工具之前,我們首先需要知道在微服務(wù)架構(gòu)系統(tǒng)中經(jīng)常會(huì)遇到兩個(gè)問題:
- 跨微服務(wù)的API調(diào)用發(fā)生異常,要求快速定位(比如5分鐘以內(nèi))出問題出在哪里,該怎么辦?
- 跨微服務(wù)的API調(diào)用發(fā)生性 能瓶頸,要求迅速定位(比如5分鐘以內(nèi))出系統(tǒng)瓶頸,該怎么辦?
一般來說要解決這兩個(gè)問題或者與之類似的問題,就需要用到調(diào)用鏈監(jiān)控工具。那么調(diào)用鏈監(jiān)控工具是怎么實(shí)現(xiàn)問題的快速定位的呢?這就需要我們理解調(diào)用鏈監(jiān)控的基礎(chǔ)實(shí)現(xiàn)原理,我們來看一張圖:

圖中有兩個(gè)微服務(wù)分別是內(nèi)容中心和用戶中心,其中內(nèi)容中心的/shares/1接口會(huì)調(diào)用用戶中心的/users/1接口,這里就產(chǎn)生了一個(gè)調(diào)用鏈。我們可以將調(diào)用的過程分為四個(gè)階段或者說狀態(tài),當(dāng)內(nèi)容中心發(fā)送調(diào)用請(qǐng)求時(shí)處于“client send”狀態(tài),用戶中心接收到調(diào)用請(qǐng)求時(shí)處于“server receive”狀態(tài),用戶中心處理完請(qǐng)求并返回結(jié)果時(shí)處于“server send”狀態(tài),最后內(nèi)容中心接收到響應(yīng)結(jié)果時(shí)處于“client receive”狀態(tài)。
假設(shè),調(diào)用鏈流轉(zhuǎn)每個(gè)狀態(tài)時(shí)都會(huì)向一張數(shù)據(jù)表里插入一些數(shù)據(jù),如下圖所示:

表字段說明:
- id:自增id
- span_id:唯一id
- pspan_id:父級(jí)span_id
- service_name:服務(wù)名稱
- api:api路徑
- stage:階段/狀態(tài)
- timestamp:插入數(shù)據(jù)時(shí)的時(shí)間戳
這是一張典型的自表一對(duì)多的表結(jié)構(gòu),根據(jù)這張表的數(shù)據(jù),就可以實(shí)現(xiàn)對(duì)以上所提到的兩個(gè)問題進(jìn)行快速定位。首先對(duì)于第一個(gè)問題,可以通過查詢表內(nèi)的數(shù)據(jù)行數(shù),判斷調(diào)用鏈在哪個(gè)階段中斷了。例如表中只有uuid1和uuid2兩條數(shù)據(jù),就可以判斷出是user-center的接口出現(xiàn)了問題,沒有正常返回結(jié)果。再如表中只有uuid1、uuid2及uuid3這三條數(shù)據(jù),就可以判斷出content-center沒有正常接收到user-center返回的結(jié)果,以此類推。如此一來,就可以通過表中的數(shù)據(jù)快速定位出跨微服務(wù)的API調(diào)用是在哪個(gè)階段發(fā)生了異常。
對(duì)于第二個(gè)問題,可以通過計(jì)算timestamp分析哪個(gè)調(diào)用比較耗時(shí)。例如上圖中的t2 - t1可以得出請(qǐng)求的發(fā)送到請(qǐng)求的接收所消耗的時(shí)間,再如t3 - t2可以得出/users/1這個(gè)接口的調(diào)用耗時(shí),而t4 - t1則可以得出整個(gè)調(diào)用鏈的耗時(shí),以此類推。所以當(dāng)跨微服務(wù)的API調(diào)用發(fā)生性能瓶頸時(shí),就可以通過分析各個(gè)調(diào)用接口的耗時(shí),快速定位出是哪個(gè)微服務(wù)接口拖慢了整個(gè)調(diào)用鏈耗時(shí)。
以上舉例簡(jiǎn)述了實(shí)現(xiàn)調(diào)用鏈監(jiān)控的基礎(chǔ)原理,雖然未必所有的調(diào)用鏈監(jiān)控工具都是這么實(shí)現(xiàn)的,但基本都異曲同工,或在其之上進(jìn)行了一些拓展。所以只要理解了這一部分,在學(xué)習(xí)各種調(diào)用鏈監(jiān)控工具時(shí)就會(huì)比較快上手。
5. Spring Cloud Sleuth簡(jiǎn)介
Spring Cloud Sleuth實(shí)現(xiàn)了一種分布式的服務(wù)鏈路跟蹤解決方案,通過使用Sleuth可以讓我們快速定位某個(gè)服務(wù)的問題。簡(jiǎn)單來說,Sleuth相當(dāng)于調(diào)用鏈監(jiān)控工具的客戶端,集成在各個(gè)微服務(wù)上,負(fù)責(zé)產(chǎn)生調(diào)用鏈監(jiān)控?cái)?shù)據(jù)。
官方文檔地址如下:
一些概念:
Span(跨度):Span是基本的工作單元。Span包括一個(gè)64位的唯一ID,一個(gè)64位trace碼,描述信息,時(shí)間戳事件,key-value 注解(tags),span處理者的ID(通常為IP)。
最開始的初始Span稱為根span,此span中span id和 trace id值相同。Trance(跟蹤):包含一系列的span,它們組成了一個(gè)樹型結(jié)構(gòu)
-
Annotation(標(biāo)注):用于及時(shí)記錄存在的事件。常用的Annotation如下:
- CS(Client Sent 客戶端發(fā)送):客戶端發(fā)送一個(gè)請(qǐng)求,表示span的開始
- SR(Server Received 服務(wù)端接收):服務(wù)端接收請(qǐng)求并開始處理它。(SR - CS)等于網(wǎng)絡(luò)的延遲
- SS(Server Sent 服務(wù)端發(fā)送):服務(wù)端處理請(qǐng)求完成,開始返回結(jié)束給服務(wù)端。(SR - SS)表示服務(wù)端處理請(qǐng)求的時(shí)間
- CR(Client Received 客戶端接收):客戶端完成接受返回結(jié)果,此時(shí)span結(jié)束。(CR - CS)表示客戶端接收服務(wù)端數(shù)據(jù)的時(shí)間
如果一個(gè)服務(wù)的調(diào)用關(guān)系如下:

那么此時(shí)將Span和Trace在一個(gè)系統(tǒng)中使用Zipkin注解的過程圖形化如下:

每個(gè)顏色的表明一個(gè)span(總計(jì)7個(gè)spans,從A到G),每個(gè)span有類似的信息
Trace Id = X
Span Id = D
Client Sent
此span表示span的Trance Id是X,Span Id是D,同時(shí)它發(fā)送一個(gè)Client Sent事件
spans 的parent/child關(guān)系圖形化如下:

6. 整合Spring Cloud Sleuth
了解完基本的一些概念后,我們來在訂單服務(wù)和商品服務(wù)中,集成spring cloud sleuth以及zipkin。在兩個(gè)服務(wù)的pom.xml文件中,增加如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
為了更詳細(xì)的查看服務(wù)通信時(shí)的日志信息,我們可以將Feign和Sleuth的日志級(jí)別設(shè)置為debug。在兩個(gè)項(xiàng)目的配置文件中,加入如下內(nèi)容即可:
logging:
level:
org.springframework.cloud.openfeign: debug
org.springframework.cloud.sleuth: debug
啟動(dòng)訂單、商品服務(wù)項(xiàng)目,然后訪問創(chuàng)建訂單的接口,訂單服務(wù)的控制臺(tái)會(huì)輸出一段這樣的信息:
[order,6c8ecdeefb0fc723,cc4109a6e8e56d1c,false]
商品服務(wù)的控制臺(tái)也會(huì)輸出類似的信息,如下:
[product,6c8ecdeefb0fc723,40cdc34e745d59e7,false]
說明:
- product: 看也知道是服務(wù)名稱
- 6c8ecdeefb0fc723: 是TranceId,一條鏈路中,只有一個(gè)TranceId
- 40cdc34e745d59e7:則是spanId,鏈路中的基本工作單元id
- false:表示是否將數(shù)據(jù)輸出到其他服務(wù),true則會(huì)把信息輸出到其他可視化的服務(wù)上觀察
7. Zipkin搭建與整合
通過Sleuth產(chǎn)生的調(diào)用鏈監(jiān)控信息,讓我們可以得知微服務(wù)之間的調(diào)用鏈路,但是監(jiān)控信息只輸出到控制臺(tái)始終不太方便查看。所以我們需要一個(gè)圖形化的工具,這時(shí)候就輪到zipkin出場(chǎng)了。
Zipkin是一款開源的分布式實(shí)時(shí)數(shù)據(jù)追蹤系統(tǒng)(Distributed Tracking System),基于 Google Dapper的論文設(shè)計(jì)而來,由 Twitter 公司開發(fā)貢獻(xiàn)。其主要功能是聚集來自各個(gè)異構(gòu)系統(tǒng)的實(shí)時(shí)監(jiān)控?cái)?shù)據(jù)。分布式跟蹤系統(tǒng)還有其他比較成熟的實(shí)現(xiàn),例如:Naver的Pinpoint、Apache的HTrace、阿里的鷹眼Tracing、京東的Hydra、新浪的Watchman,美團(tuán)點(diǎn)評(píng)的CAT,skywalking等。
zipkin官網(wǎng)地址如下:
ZipKin可以分為兩部分,一部分是zipkin server,用來作為數(shù)據(jù)的采集存儲(chǔ)、數(shù)據(jù)分析與展示;zipkin client是zipkin基于不同的語(yǔ)言及框架封裝的一些列客戶端工具,這些工具完成了追蹤數(shù)據(jù)的生成與上報(bào)功能,架構(gòu)如下:

Zipkin Server主要包括四個(gè)模塊:
(1)Collector 接收或收集各應(yīng)用傳輸?shù)臄?shù)據(jù)
(2)Storage 存儲(chǔ)接受或收集過來的數(shù)據(jù),當(dāng)前支持Memory,MySQL,Cassandra,ElasticSearch等,默認(rèn)存儲(chǔ)在內(nèi)存中。
(3)API(Query) 負(fù)責(zé)查詢Storage中存儲(chǔ)的數(shù)據(jù),提供簡(jiǎn)單的JSON API獲取數(shù)據(jù),主要提供給web UI使用
(4)Web 提供簡(jiǎn)單的web界面
ZipKin幾個(gè)概念
在追蹤日志中,有幾個(gè)基本概念spanId、traceId、parentId
- traceId:用來確定一個(gè)追蹤鏈的16字符長(zhǎng)度的字符串,在某個(gè)追蹤鏈中保持不變。
- spanId:區(qū)域Id,在一個(gè)追蹤鏈中spanId可能存在多個(gè),每個(gè)spanId用于表明在某個(gè)服務(wù)中的身份,也是16字符長(zhǎng)度的字符串。
- parentId:在跨服務(wù)調(diào)用者的spanId會(huì)傳遞給被調(diào)用者,被調(diào)用者會(huì)將調(diào)用者的spanId作為自己的parentId,然后自己再生成spanId。
如下圖:
剛發(fā)起調(diào)用時(shí)traceId和spanId是一致,parentId不存在。

被調(diào)用者的traceId和調(diào)用者的traceId時(shí)一致的,被調(diào)用者會(huì)產(chǎn)生自己的spanId,并且被調(diào)用者的parentId是調(diào)用者的spanId

接下來我們搭建一個(gè)zipkin服務(wù)器。
方式1,使用Zipkin官方的Shell下載,使用如下命令可下載最新版本:
[root@01server ~]# curl -sSL https://zipkin.io/quickstart.sh | bash -s
下載下來的文件名為 zipkin.jar
方式2,到Maven中央倉(cāng)庫(kù)下載,使用瀏覽器訪問如下地址即可:
https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec
下載下來的文件名為 zipkin-server-{版本號(hào)}-exec.jar
由于Zipkin實(shí)際是一個(gè)Spring Boot項(xiàng)目,所以使用以上兩種方式下載的jar包,可以直接使用如下命令啟動(dòng):
java jar {zipkin jar包路徑}
方式3,通過docker安裝,命令如下:
[root@01server ~]# docker run -d -p 9411:9411 openzipkin/zipkin
安裝好后,使用瀏覽器訪問9411端口,主頁(yè)面如下所示:

然后在訂單服務(wù)中將之前的sleuth依賴替換成如下依賴:
<!-- 這個(gè)依賴包含了sleuth和zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
在配置文件中,增加zipkin相關(guān)的配置項(xiàng)。如下:
spring:
...
zipkin:
base-url: http://127.0.0.1:9411/ # zipkin服務(wù)器的地址
# 關(guān)閉服務(wù)發(fā)現(xiàn),否則Spring Cloud會(huì)把zipkin的url當(dāng)做服務(wù)名稱
discoveryClientEnabled: false
sender:
type: web # 設(shè)置使用http的方式傳輸數(shù)據(jù)
sleuth:
sampler:
probability: 1 # 設(shè)置抽樣采集率為100%,默認(rèn)為0.1,即10%
配置好后重啟項(xiàng)目,并訪問創(chuàng)建訂單接口。下單成功后,到zipkin頁(yè)面上就可以查看到order服務(wù)的鏈路信息了:

會(huì)有紅色的信息表示有錯(cuò)誤,點(diǎn)擊上圖中的紅色信息后,可以進(jìn)入到服務(wù)鏈路的查看頁(yè)面,在這里可以看到整條服務(wù)鏈路,并且可以看到每一個(gè)服務(wù)調(diào)用的耗時(shí),也可以看到是哪一步調(diào)用發(fā)生了錯(cuò)誤:

點(diǎn)擊每一行信息都可以查看其詳情信息,例如我點(diǎn)擊耗時(shí)46.236ms的那行信息,其詳細(xì)信息如下:

8. Zipkin數(shù)據(jù)持久化
Zipkin默認(rèn)是將監(jiān)控?cái)?shù)據(jù)存儲(chǔ)在內(nèi)存的,如果Zipkin掛掉或重啟的話,那么監(jiān)控?cái)?shù)據(jù)就會(huì)丟失。所以如果想要搭建生產(chǎn)可用的Zipkin,就需要實(shí)現(xiàn)監(jiān)控?cái)?shù)據(jù)的持久化。而想要實(shí)現(xiàn)數(shù)據(jù)持久化,自然就是得將數(shù)據(jù)存儲(chǔ)至數(shù)據(jù)庫(kù)。好在Zipkin支持將數(shù)據(jù)存儲(chǔ)至:
- 內(nèi)存(默認(rèn))
- MySQL
- Elasticsearch
- Cassandra
Zipkin數(shù)據(jù)持久化相關(guān)的官方文檔地址如下:
Zipkin支持的這幾種存儲(chǔ)方式中,內(nèi)存顯然是不適用于生產(chǎn)的,這一點(diǎn)開始也說了。而使用MySQL的話,當(dāng)數(shù)據(jù)量大時(shí),查詢較為緩慢,也不建議使用。Twitter官方使用的是Cassandra作為Zipkin的存儲(chǔ)數(shù)據(jù)庫(kù),但國(guó)內(nèi)大規(guī)模用Cassandra的公司較少,而且Cassandra相關(guān)文檔也不多。
綜上,故采用Elasticsearch是個(gè)比較好的選擇,關(guān)于使用Elasticsearch作為Zipkin的存儲(chǔ)數(shù)據(jù)庫(kù)的官方文檔如下:
既然選擇Elasticsearch作為Zipkin的存儲(chǔ)數(shù)據(jù)庫(kù),那么自然首先需要搭建一個(gè)Elasticsearch服務(wù),單節(jié)點(diǎn)搭建比較簡(jiǎn)單,直接到官網(wǎng)下載壓縮包,然后使用如下命令解壓并啟動(dòng)即可(關(guān)于ES的版本選擇需參考官方文檔,目前Zipkin支持5.x、6.x及7.x):
[root@01server ~]# tar -zxvf elasticsearch-6.5.3-linux-x86_64.tar.gz # 解壓
[root@01server ~]# cd elasticsearch-6.5.3/bin
[root@01server ~/elasticsearch-6.5.3/bin]# ./elasticsearch # 啟動(dòng)
由于Elasticsearch不是本文的重點(diǎn),這里不做不多的介紹,關(guān)于Elasticsearch的集群搭建可以參考如下文章:
- 使用docker安裝elasticsearch偽分布式集群以及安裝ik中文分詞插件
- 搭建ELK日志分析平臺(tái)(上)—— ELK介紹及搭建 Elasticsearch 分布式集群
- CentOS7 下安裝 ElasticSearch 5.x 及填坑
搭建好Elasticsearch后,使用如下命令啟動(dòng)Zipkin,Zipkin就會(huì)切換存儲(chǔ)類型為Elasticsearch,然后根據(jù)指定的連接地址連接Elasticsearch并存儲(chǔ)數(shù)據(jù):
STORAGE_TYPE=elasticsearch ES_HOSTS=localhost:9200 java -jar zipkin-server-2.11.3-exec.jar
Tips:
-
其中,
STORAGE_TYPE和ES_HOSTS是環(huán)境變量,STORAGE_TYPE用于指定Zipkin的存儲(chǔ)類型是啥;而ES_HOSTS則用于指定Elasticsearch地址列表,有多個(gè)節(jié)點(diǎn)時(shí)使用逗號(hào)( , )分隔。
除此之外,還可以指定其他環(huán)境變量,參考下表:image
關(guān)于其他環(huán)境變量,可參考官方文檔:
最后可以根據(jù)以下測(cè)試步驟,自行測(cè)試一下Zipkin是否能正常將監(jiān)控?cái)?shù)據(jù)持久化存儲(chǔ):
- 往Zipkin中存儲(chǔ)一些數(shù)據(jù)
- 停止Zipkin
- 再次啟動(dòng)Zipkin,查看之前存儲(chǔ)的數(shù)據(jù)是否存在,如果存在說明數(shù)據(jù)已被持久化
關(guān)于依賴關(guān)系圖的問題
在上一小節(jié)中,簡(jiǎn)單介紹了Zipkin的數(shù)據(jù)持久化,并整合了Elasticsearch作為Zipkin的存儲(chǔ)數(shù)據(jù)庫(kù)。但此時(shí)會(huì)有一個(gè)問題,就是Zipkin在整合Elasticsearch后會(huì)無(wú)法分析服務(wù)之間的依賴關(guān)系圖,因?yàn)榇藭r(shí)數(shù)據(jù)都存儲(chǔ)到Elasticsearch中了,無(wú)法再像之前那樣在內(nèi)存中進(jìn)行分析。
想要解決這個(gè)問題,需要下載并使用Zipkin的一個(gè)子項(xiàng)目:
方式1,使用官方的Shell下載,使用如下命令可下載最新版本:
[root@01server ~]# curl -sSL https://zipkin.io/quickstart.sh | bash -s io.zipkin.dependencies:zipkin-dependencies:LATEST zipkin-dependencies.jar
下載下來的文件名為 zipkin-dependencies.jar
方式2,到Maven中央倉(cāng)庫(kù)下載,使用瀏覽器訪問如下地址即可:
https://search.maven.org/remote_content?g=io.zipkin.dependencies&a=zipkin-dependencies&v=LATEST
下載下來的文件名為 zipkin-dependencies-{版本號(hào)}.jar
下載好后,使用如下命令運(yùn)行這個(gè)jar包即可分析Elasticsearch中存儲(chǔ)的數(shù)據(jù):
[root@01server ~]# STORAGE_TYPE=elasticsearch ES_HOSTS=localhost:9200 java -jar zipkin-dependencies-2.3.2.jar
該jar包運(yùn)行結(jié)束后,到Zipkin的界面上點(diǎn)開“Dependencies”就可以正常查看到依賴關(guān)系圖了。
方式3,通過docker下載并運(yùn)行,命令如下:
[root@01server ~]# docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.190.129:9200 openzipkin/zipkin-dependencies
Tips:
這個(gè)Zipkin Dependencies屬于是一個(gè)job,不是服務(wù),即不會(huì)持續(xù)運(yùn)行,而是每運(yùn)行一次才分析數(shù)據(jù)。若想持續(xù)運(yùn)行的話,需要自己寫個(gè)定時(shí)腳本來定時(shí)運(yùn)行這個(gè)job
使用Elasticsearch時(shí)Zipkin Dependencies支持的環(huán)境變量:

Zipkin Dependencies支持的其他環(huán)境變量:
Zipkin Dependencies默認(rèn)分析的是當(dāng)天的數(shù)據(jù),可以通過如下命令讓Zipkin Dependencies分析指定日期的數(shù)據(jù):

