1. 什么是調用鏈
一個業務功能可能需要多個服務協作才能實現,一個請求到達服務A,服務A需要依賴服務B,服務B又依賴服務C,甚至C仍需依賴其他服務,形成一個調用鏈條,即調用鏈。
2. 為什么要監控調用鏈
上圖傳遞了一個信息,就是微服務的復雜性
- 出現問題后,定位困難,需要對整個調用鏈路有個完善的監控
- 鏈路復雜,需要清晰的鏈路圖譜反映服務之間的依賴、調用關系
- 整體系統性能及運行情況,需要明確的體現,才能根據實際情況調整資源
3. 要監控哪些方面
- 圖形化展示整個調用鏈路
- 系統的性能指標
- 健康狀況
- 基礎告警
4. 調用鏈監控的基礎原理
在介紹調用鏈監控工具之前,我們首先需要知道在微服務架構系統中經常會遇到兩個問題:
- 跨微服務的API調用發生異常,要求快速定位(比如5分鐘以內)出問題出在哪里,該怎么辦?
- 跨微服務的API調用發生性 能瓶頸,要求迅速定位(比如5分鐘以內)出系統瓶頸,該怎么辦?
一般來說要解決這兩個問題或者與之類似的問題,就需要用到調用鏈監控工具。那么調用鏈監控工具是怎么實現問題的快速定位的呢?這就需要我們理解調用鏈監控的基礎實現原理,我們來看一張圖:
圖中有兩個微服務分別是內容中心和用戶中心,其中內容中心的/shares/1接口會調用用戶中心的/users/1接口,這里就產生了一個調用鏈。我們可以將調用的過程分為四個階段或者說狀態,當內容中心發送調用請求時處于“client send”狀態,用戶中心接收到調用請求時處于“server receive”狀態,用戶中心處理完請求并返回結果時處于“server send”狀態,最后內容中心接收到響應結果時處于“client receive”狀態。
假設,調用鏈流轉每個狀態時都會向一張數據表里插入一些數據,如下圖所示:
表字段說明:
- id:自增id
- span_id:唯一id
- pspan_id:父級span_id
- service_name:服務名稱
- api:api路徑
- stage:階段/狀態
- timestamp:插入數據時的時間戳
這是一張典型的自表一對多的表結構,根據這張表的數據,就可以實現對以上所提到的兩個問題進行快速定位。首先對于第一個問題,可以通過查詢表內的數據行數,判斷調用鏈在哪個階段中斷了。例如表中只有uuid1和uuid2兩條數據,就可以判斷出是user-center的接口出現了問題,沒有正常返回結果。再如表中只有uuid1、uuid2及uuid3這三條數據,就可以判斷出content-center沒有正常接收到user-center返回的結果,以此類推。如此一來,就可以通過表中的數據快速定位出跨微服務的API調用是在哪個階段發生了異常。
對于第二個問題,可以通過計算timestamp分析哪個調用比較耗時。例如上圖中的t2 - t1可以得出請求的發送到請求的接收所消耗的時間,再如t3 - t2可以得出/users/1這個接口的調用耗時,而t4 - t1則可以得出整個調用鏈的耗時,以此類推。所以當跨微服務的API調用發生性能瓶頸時,就可以通過分析各個調用接口的耗時,快速定位出是哪個微服務接口拖慢了整個調用鏈耗時。
以上舉例簡述了實現調用鏈監控的基礎原理,雖然未必所有的調用鏈監控工具都是這么實現的,但基本都異曲同工,或在其之上進行了一些拓展。所以只要理解了這一部分,在學習各種調用鏈監控工具時就會比較快上手。
5. Spring Cloud Sleuth簡介
Spring Cloud Sleuth實現了一種分布式的服務鏈路跟蹤解決方案,通過使用Sleuth可以讓我們快速定位某個服務的問題。簡單來說,Sleuth相當于調用鏈監控工具的客戶端,集成在各個微服務上,負責產生調用鏈監控數據。
官方文檔地址如下:
一些概念:
Span(跨度):Span是基本的工作單元。Span包括一個64位的唯一ID,一個64位trace碼,描述信息,時間戳事件,key-value 注解(tags),span處理者的ID(通常為IP)。
最開始的初始Span稱為根span,此span中span id和 trace id值相同。Trance(跟蹤):包含一系列的span,它們組成了一個樹型結構
-
Annotation(標注):用于及時記錄存在的事件。常用的Annotation如下:
- CS(Client Sent 客戶端發送):客戶端發送一個請求,表示span的開始
- SR(Server Received 服務端接收):服務端接收請求并開始處理它。(SR - CS)等于網絡的延遲
- SS(Server Sent 服務端發送):服務端處理請求完成,開始返回結束給服務端。(SR - SS)表示服務端處理請求的時間
- CR(Client Received 客戶端接收):客戶端完成接受返回結果,此時span結束。(CR - CS)表示客戶端接收服務端數據的時間
如果一個服務的調用關系如下:
那么此時將Span和Trace在一個系統中使用Zipkin注解的過程圖形化如下:
每個顏色的表明一個span(總計7個spans,從A到G),每個span有類似的信息
Trace Id = X
Span Id = D
Client Sent
此span表示span的Trance Id是X,Span Id是D,同時它發送一個Client Sent事件
spans 的parent/child關系圖形化如下:
6. 整合Spring Cloud Sleuth
了解完基本的一些概念后,我們來在訂單服務和商品服務中,集成spring cloud sleuth以及zipkin。在兩個服務的pom.xml文件中,增加如下依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
為了更詳細的查看服務通信時的日志信息,我們可以將Feign和Sleuth的日志級別設置為debug。在兩個項目的配置文件中,加入如下內容即可:
logging:
level:
org.springframework.cloud.openfeign: debug
org.springframework.cloud.sleuth: debug
啟動訂單、商品服務項目,然后訪問創建訂單的接口,訂單服務的控制臺會輸出一段這樣的信息:
[order,6c8ecdeefb0fc723,cc4109a6e8e56d1c,false]
商品服務的控制臺也會輸出類似的信息,如下:
[product,6c8ecdeefb0fc723,40cdc34e745d59e7,false]
說明:
- product: 看也知道是服務名稱
- 6c8ecdeefb0fc723: 是TranceId,一條鏈路中,只有一個TranceId
- 40cdc34e745d59e7:則是spanId,鏈路中的基本工作單元id
- false:表示是否將數據輸出到其他服務,true則會把信息輸出到其他可視化的服務上觀察
7. Zipkin搭建與整合
通過Sleuth產生的調用鏈監控信息,讓我們可以得知微服務之間的調用鏈路,但是監控信息只輸出到控制臺始終不太方便查看。所以我們需要一個圖形化的工具,這時候就輪到zipkin出場了。
Zipkin是一款開源的分布式實時數據追蹤系統(Distributed Tracking System),基于 Google Dapper的論文設計而來,由 Twitter 公司開發貢獻。其主要功能是聚集來自各個異構系統的實時監控數據。分布式跟蹤系統還有其他比較成熟的實現,例如:Naver的Pinpoint、Apache的HTrace、阿里的鷹眼Tracing、京東的Hydra、新浪的Watchman,美團點評的CAT,skywalking等。
zipkin官網地址如下:
ZipKin可以分為兩部分,一部分是zipkin server,用來作為數據的采集存儲、數據分析與展示;zipkin client是zipkin基于不同的語言及框架封裝的一些列客戶端工具,這些工具完成了追蹤數據的生成與上報功能,架構如下:
Zipkin Server主要包括四個模塊:
(1)Collector 接收或收集各應用傳輸的數據
(2)Storage 存儲接受或收集過來的數據,當前支持Memory,MySQL,Cassandra,ElasticSearch等,默認存儲在內存中。
(3)API(Query) 負責查詢Storage中存儲的數據,提供簡單的JSON API獲取數據,主要提供給web UI使用
(4)Web 提供簡單的web界面
ZipKin幾個概念
在追蹤日志中,有幾個基本概念spanId、traceId、parentId
- traceId:用來確定一個追蹤鏈的16字符長度的字符串,在某個追蹤鏈中保持不變。
- spanId:區域Id,在一個追蹤鏈中spanId可能存在多個,每個spanId用于表明在某個服務中的身份,也是16字符長度的字符串。
- parentId:在跨服務調用者的spanId會傳遞給被調用者,被調用者會將調用者的spanId作為自己的parentId,然后自己再生成spanId。
如下圖:
剛發起調用時traceId和spanId是一致,parentId不存在。
被調用者的traceId和調用者的traceId時一致的,被調用者會產生自己的spanId,并且被調用者的parentId是調用者的spanId
接下來我們搭建一個zipkin服務器。
方式1,使用Zipkin官方的Shell下載,使用如下命令可下載最新版本:
[root@01server ~]# curl -sSL https://zipkin.io/quickstart.sh | bash -s
下載下來的文件名為 zipkin.jar
方式2,到Maven中央倉庫下載,使用瀏覽器訪問如下地址即可:
https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec
下載下來的文件名為 zipkin-server-{版本號}-exec.jar
由于Zipkin實際是一個Spring Boot項目,所以使用以上兩種方式下載的jar包,可以直接使用如下命令啟動:
java jar {zipkin jar包路徑}
方式3,通過docker安裝,命令如下:
[root@01server ~]# docker run -d -p 9411:9411 openzipkin/zipkin
安裝好后,使用瀏覽器訪問9411端口,主頁面如下所示:
然后在訂單服務中將之前的sleuth依賴替換成如下依賴:
<!-- 這個依賴包含了sleuth和zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
在配置文件中,增加zipkin相關的配置項。如下:
spring:
...
zipkin:
base-url: http://127.0.0.1:9411/ # zipkin服務器的地址
# 關閉服務發現,否則Spring Cloud會把zipkin的url當做服務名稱
discoveryClientEnabled: false
sender:
type: web # 設置使用http的方式傳輸數據
sleuth:
sampler:
probability: 1 # 設置抽樣采集率為100%,默認為0.1,即10%
配置好后重啟項目,并訪問創建訂單接口。下單成功后,到zipkin頁面上就可以查看到order服務的鏈路信息了:
會有紅色的信息表示有錯誤,點擊上圖中的紅色信息后,可以進入到服務鏈路的查看頁面,在這里可以看到整條服務鏈路,并且可以看到每一個服務調用的耗時,也可以看到是哪一步調用發生了錯誤:
點擊每一行信息都可以查看其詳情信息,例如我點擊耗時46.236ms的那行信息,其詳細信息如下:
8. Zipkin數據持久化
Zipkin默認是將監控數據存儲在內存的,如果Zipkin掛掉或重啟的話,那么監控數據就會丟失。所以如果想要搭建生產可用的Zipkin,就需要實現監控數據的持久化。而想要實現數據持久化,自然就是得將數據存儲至數據庫。好在Zipkin支持將數據存儲至:
- 內存(默認)
- MySQL
- Elasticsearch
- Cassandra
Zipkin數據持久化相關的官方文檔地址如下:
Zipkin支持的這幾種存儲方式中,內存顯然是不適用于生產的,這一點開始也說了。而使用MySQL的話,當數據量大時,查詢較為緩慢,也不建議使用。Twitter官方使用的是Cassandra作為Zipkin的存儲數據庫,但國內大規模用Cassandra的公司較少,而且Cassandra相關文檔也不多。
綜上,故采用Elasticsearch是個比較好的選擇,關于使用Elasticsearch作為Zipkin的存儲數據庫的官方文檔如下:
既然選擇Elasticsearch作為Zipkin的存儲數據庫,那么自然首先需要搭建一個Elasticsearch服務,單節點搭建比較簡單,直接到官網下載壓縮包,然后使用如下命令解壓并啟動即可(關于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 # 啟動
由于Elasticsearch不是本文的重點,這里不做不多的介紹,關于Elasticsearch的集群搭建可以參考如下文章:
- 使用docker安裝elasticsearch偽分布式集群以及安裝ik中文分詞插件
- 搭建ELK日志分析平臺(上)—— ELK介紹及搭建 Elasticsearch 分布式集群
- CentOS7 下安裝 ElasticSearch 5.x 及填坑
搭建好Elasticsearch后,使用如下命令啟動Zipkin,Zipkin就會切換存儲類型為Elasticsearch,然后根據指定的連接地址連接Elasticsearch并存儲數據:
STORAGE_TYPE=elasticsearch ES_HOSTS=localhost:9200 java -jar zipkin-server-2.11.3-exec.jar
Tips:
- 其中,
STORAGE_TYPE
和ES_HOSTS
是環境變量,STORAGE_TYPE
用于指定Zipkin的存儲類型是啥;而ES_HOSTS
則用于指定Elasticsearch地址列表,有多個節點時使用逗號( , )分隔。
除此之外,還可以指定其他環境變量,參考下表:
關于其他環境變量,可參考官方文檔:
最后可以根據以下測試步驟,自行測試一下Zipkin是否能正常將監控數據持久化存儲:
- 往Zipkin中存儲一些數據
- 停止Zipkin
- 再次啟動Zipkin,查看之前存儲的數據是否存在,如果存在說明數據已被持久化
關于依賴關系圖的問題
在上一小節中,簡單介紹了Zipkin的數據持久化,并整合了Elasticsearch作為Zipkin的存儲數據庫。但此時會有一個問題,就是Zipkin在整合Elasticsearch后會無法分析服務之間的依賴關系圖,因為此時數據都存儲到Elasticsearch中了,無法再像之前那樣在內存中進行分析。
想要解決這個問題,需要下載并使用Zipkin的一個子項目:
方式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中央倉庫下載,使用瀏覽器訪問如下地址即可:
https://search.maven.org/remote_content?g=io.zipkin.dependencies&a=zipkin-dependencies&v=LATEST
下載下來的文件名為 zipkin-dependencies-{版本號}.jar
下載好后,使用如下命令運行這個jar包即可分析Elasticsearch中存儲的數據:
[root@01server ~]# STORAGE_TYPE=elasticsearch ES_HOSTS=localhost:9200 java -jar zipkin-dependencies-2.3.2.jar
該jar包運行結束后,到Zipkin的界面上點開“Dependencies”就可以正常查看到依賴關系圖了。
方式3,通過docker下載并運行,命令如下:
[root@01server ~]# docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.190.129:9200 openzipkin/zipkin-dependencies
Tips:
這個Zipkin Dependencies屬于是一個job,不是服務,即不會持續運行,而是每運行一次才分析數據。若想持續運行的話,需要自己寫個定時腳本來定時運行這個job
使用Elasticsearch時Zipkin Dependencies支持的環境變量:
Zipkin Dependencies支持的其他環境變量:
Zipkin Dependencies默認分析的是當天的數據,可以通過如下命令讓Zipkin Dependencies分析指定日期的數據: