分布式進(jìn)階-鏈路追蹤SpringCloudSleuth、Zipkin【實(shí)戰(zhàn)篇】
一、前言
我們?cè)谑褂梦⒎?wù)的時(shí)候,往往涉及到各個(gè)微服務(wù)之間的調(diào)用,肯定會(huì)存在深度的調(diào)用鏈路,如果出現(xiàn)BUG或者異常,就會(huì)讓問題定位和處理效率非常低。有了Sleuth ,就可以幫助我們記錄、跟蹤應(yīng)用程序中的請(qǐng)求和操作。通常與 Zipkin 配合使用,從而提供更全面的可視化應(yīng)用程序跟蹤和分析功能。
就像ElasticSearch和Kibana一樣!
復(fù)雜的鏈路調(diào)用如下圖所示:
在繼續(xù)往下看的同時(shí),需要你具備Springboot整合Nacos構(gòu)建一個(gè)聚合項(xiàng)目的能力。
當(dāng)然如果不想自己來,小編也給大家準(zhǔn)備好了。大家可以下載運(yùn)行一下,開始下面的實(shí)戰(zhàn)!
防止Github訪問不了,這里把代碼提交到了Gitee。
cloud-sleuth-zipkin-demo代碼下載地址:https://gitee.com/wang-zhenjun/cloud-sleuth-zipkin-demo
二、Spring Cloud Sleuth 介紹
1、簡(jiǎn)介
Spring Cloud Sleuth 是 Spring Cloud 生態(tài)系統(tǒng)的一部分,它是一個(gè)分布式追蹤解決方案,用于監(jiān)視微服務(wù)架構(gòu)中的請(qǐng)求流程,并幫助開發(fā)者跟蹤請(qǐng)求在不同微服務(wù)之間的傳播路徑。
Sleuth主要用于解決微服務(wù)架構(gòu)中的分布式系統(tǒng)跟蹤和調(diào)試問題。
官網(wǎng)文檔:https://docs.spring.io/spring-cloud-sleuth/docs/2.2.8.RELEASE/reference/html/。
2、核心概念
我們可以看一下官網(wǎng)的圖片:
簡(jiǎn)單名詞介紹:
?
cs:「客戶端發(fā)送」,客戶已提出請(qǐng)求。該注釋指示跨度的開始。sr:「服務(wù)器已接收」,服務(wù)器端收到請(qǐng)求并開始處理。從此時(shí)間戳中減去cs時(shí)間戳即可得出網(wǎng)絡(luò)延遲。ss:「服務(wù)器發(fā)送」,在請(qǐng)求處理完成時(shí)(當(dāng)響應(yīng)發(fā)送回客戶端時(shí))進(jìn)行注釋。從這個(gè)時(shí)間戳中減去sr時(shí)間戳就可以得出服務(wù)器端處理請(qǐng)求所需的時(shí)間。cr:「客戶端已收到」,表示跨度的結(jié)束。客戶端已成功收到服務(wù)器端的響應(yīng)。從此時(shí)間戳中減去cs時(shí)間戳即可得出客戶端從服務(wù)器接收響應(yīng)所需的整個(gè)時(shí)間。
?
詳細(xì)信息可以看官網(wǎng)介紹,總結(jié)一下:
名詞 | 翻譯 | 解釋 |
Trace | 追蹤 | Trace 是一個(gè)請(qǐng)求的整體追蹤。它代表了從請(qǐng)求的起始點(diǎn)到結(jié)束點(diǎn)的完整路徑,經(jīng)過多個(gè)微服務(wù)。每個(gè) Trace 都有一個(gè)唯一的 Trace ID。 |
Span | 跨度 | Span 是 Trace 中的一個(gè)小段,它代表了請(qǐng)求在某個(gè)特定微服務(wù)上的處理過程。Spans 之間有父子關(guān)系,它們可以形成一個(gè)層次結(jié)構(gòu),以表示請(qǐng)求的處理路徑。 |
Trace ID | 追蹤標(biāo)識(shí) | Trace ID 是唯一標(biāo)識(shí)一個(gè) Trace 的標(biāo)識(shí)符。它在整個(gè) Trace 中保持不變,用于將不同的 Span 關(guān)聯(lián)到同一個(gè) Trace 上。 |
Span ID | 跨度標(biāo)識(shí) | Span ID 是唯一標(biāo)識(shí)一個(gè) Span 的標(biāo)識(shí)符。它用于在不同 Span 之間建立父子關(guān)系。 |
Parent Span ID | 父 Span 標(biāo)識(shí) | 父 Span ID 是標(biāo)識(shí)一個(gè) Span 的父 Span 的標(biāo)識(shí)符。它用于建立 Span 之間的關(guān)系。 |
Annotations | 注解 | Annotations 是關(guān)于 Span 的額外信息,通常用于記錄 Span 的開始和結(jié)束時(shí)間、操作名稱、以及其他相關(guān)信息。Annotations 可以幫助你更好地理解請(qǐng)求的處理過程。 |
Binary Annotations | 二進(jìn)制注解 | Binary Annotations 是鍵值對(duì)形式的信息,用于記錄與 Span 相關(guān)的自定義信息,例如請(qǐng)求的狀態(tài)、錯(cuò)誤信息等。 |
Collector | 收集器 | Collector 是用于收集追蹤信息的組件,它將追蹤數(shù)據(jù)發(fā)送到后端存儲(chǔ)或可視化工具(如Zipkin或Jaeger)。Collector 可以將 Span 數(shù)據(jù)持久化,以供分析和監(jiān)視使用。 |
Sampler | 采樣器 | Sampler 用于確定是否對(duì)一個(gè)請(qǐng)求進(jìn)行追蹤。它決定是否為請(qǐng)求創(chuàng)建一個(gè) Trace。Sampler 可以根據(jù)策略決定是否記錄某個(gè)請(qǐng)求的 Trace 數(shù)據(jù),以避免記錄過多的追蹤信息,從而降低性能開銷。 |
「官網(wǎng)的圖,每一個(gè)代表一個(gè)組件,他們之間進(jìn)行調(diào)用,畫的少了Trace Id,加上就好了。」
每個(gè)組件都會(huì)生成一個(gè) Trace Id(全局唯一),還會(huì)有 Span Id、Parent Id 三部分組成。鏈路上的所有組件組成一個(gè)完整的 Trace。
「注意:」
「頭鏈路Parent Id = null,其余的都指向上一個(gè)組件的Span Id,從而形成鏈路。」
「一次鏈路調(diào)用所有的組件Trace Id都是一樣的。」
「這里說的組件就是一個(gè)個(gè)的微服務(wù)!」
三、 Zipkin介紹和搭建
1、定義
Zipkin 是一個(gè)分布式追蹤系統(tǒng)。它有助于收集解決服務(wù)架構(gòu)中的延遲問題所需的計(jì)時(shí)數(shù)據(jù)。功能包括該數(shù)據(jù)的收集和查找。
Zipkin官網(wǎng)地址:https://zipkin.io/。
2、核心概念
名詞 | 翻譯 | 解釋 |
Trace | 追蹤 | Trace 代表整個(gè)請(qǐng)求的追蹤路徑,跨越不同的服務(wù)。 |
Span | 跨度 | Span 是基本工作單位,代表了請(qǐng)求在單個(gè)服務(wù)中的處理過程。 |
Trace ID | 追蹤標(biāo)識(shí) | Trace ID 是唯一標(biāo)識(shí)一個(gè) Trace 的標(biāo)識(shí)符,用于將不同的 Span 關(guān)聯(lián)到同一個(gè) Trace 上。 |
Annotations | 注解 | Annotations 用于記錄 Span 的關(guān)鍵事件,通常包括開始和結(jié)束時(shí)間、操作名稱等。 |
Binary Annotations | 二進(jìn)制注解 | Binary Annotations 用于記錄額外的自定義信息,例如請(qǐng)求狀態(tài)、錯(cuò)誤信息等。 |
Collector | 收集器 | Collector 負(fù)責(zé)接收和存儲(chǔ)從不同服務(wù)發(fā)送的 Span 數(shù)據(jù),以便后續(xù)的檢查和分析。 |
Query and Visualization | 查詢和可視化 | 提供了查詢和可視化界面,允許用戶查看和分析跟蹤數(shù)據(jù),以幫助故障排查和性能優(yōu)化。 |
盡管Sleuth 和 Zipkin有些術(shù)語和概念中有相似之處,但它們是兩個(gè)不同的工具,各自有自己的實(shí)現(xiàn)和用途。
「Spring Cloud Sleuth 用于生成和傳播跟蹤信息,而 Zipkin 用于收集、存儲(chǔ)、查詢和可視化這些信息。它們可以協(xié)同工作,但也可以獨(dú)立使用。」
3、docker搭建
官方有三種方式搭建,推薦使用:「如果您熟悉 Docker,這是首選的啟動(dòng)方法。」
Docker Zipkin項(xiàng)目能夠構(gòu)建 docker 鏡像、提供腳本和docker-compose.yml 用于啟動(dòng)預(yù)構(gòu)建鏡像的腳本。
https://github.com/openzipkin/docker-zipkin/blob/master/docker-compose.yml。
最快的啟動(dòng)方式是直接運(yùn)行最新的鏡像:
docker run -d -p 9411:9411 openzipkin/zipkin
我們啟動(dòng)成功,在Windows下訪問看是否成功!
http://192.168.239.130:9411/zipkin/
「注意:」
「Zipkin默認(rèn)將追蹤數(shù)據(jù)信息保存到內(nèi)存,重啟服務(wù)后追蹤數(shù)據(jù)丟失,Zipkin支持將追蹤數(shù)據(jù)持久化到MySQL或ES。」
可以直接使用docker-componse運(yùn)行:
docker-componse運(yùn)行腳本:https://github.com/openzipkin/zipkin/tree/master/docker/examples
可以自行試一下,這里就不帶大家演示了!
四、Springboot整合
今天我們來進(jìn)行簡(jiǎn)單的鏈路模擬:
「service-order模塊調(diào)用service-stock模塊調(diào)用service-message模塊」
「通信我們使用openFeign來進(jìn)行調(diào)用,三個(gè)模塊統(tǒng)一使用nacos進(jìn)行注冊(cè)」
大家可以下載一下項(xiàng)目體驗(yàn)一下,可以自己搭建,就是一個(gè)聚合項(xiàng)目!
結(jié)構(gòu)如下:
1、導(dǎo)入依賴
這是父依賴。
<properties>
<spring.boot.version>2.7.3</spring.boot.version>
<spring.cloud.dependencies.version>2021.0.1</spring.cloud.dependencies.version>
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<org.projectlombok.lombok>1.18.26</org.projectlombok.lombok>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.lombok}</version>
</dependency>
</dependencies>
</dependencyManagement>
spring-cloud-dependencies里包含了sleuth、zipkin的依賴,父不需要再定義管理版本。
子依賴要比父依賴多了sleuth、zipkin兩個(gè),還有openFeign的包!
<!-- Sleuth依賴項(xiàng) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!--Zipkin 依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2、yml配置
三份自己改一下端口和應(yīng)用名稱:service-order、service-stock、service-message;端口分別為:9000、9001、9002
server:
port: 9000
spring:
application:
# 應(yīng)用名稱
name: service-order
cloud:
nacos:
discovery:
# 服務(wù)注冊(cè)地址
server-addr: localhost:8848
zipkin:
base-url: http://192.168.239.130:9411
sender:
type: web # 設(shè)置使用 http 的方式傳輸數(shù)據(jù)
3、詳細(xì)代碼
記得在啟動(dòng)類上添加注解:@EnableFeignClients,表示開啟feign調(diào)用。
完整的結(jié)構(gòu)如下:
下面把具體代碼給大家:OrderController :
/**
* @author wangzhenjun
* @date 2023/10/31 14:25
*/
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/order")
@RestController
public class OrderController {
private final RemoteStockFeignService remoteStockFeignService;
/**
* 模擬下單流程
* @param userId
* @param productId
* @return
*/
@GetMapping("/createOrder")
public String createOrder(@RequestParam("userId") Integer userId, @RequestParam("productId") Integer productId) {
log.info("====>訂單模塊<========");
// 調(diào)用庫存服務(wù)進(jìn)行庫存扣減
String stockResult = remoteStockFeignService.subtractStock(userId,productId,1);
log.info("扣減庫存結(jié)果:{}", stockResult);
// 還有其他。。。。。
return "下單成功!";
}
}
RemoteStockFeignService :
/**
* @author wangzhenjun
* @date 2023/10/31 14:29
*/
@FeignClient(value = "service-stock")
public interface RemoteStockFeignService {
@GetMapping(value = "/stock/subtractStock")
String subtractStock(@RequestParam(value = "userId") Integer userId,@RequestParam(value = "productId") Integer productId,@RequestParam(value = "num") Integer num);
}
StockController :
/**
* @author wangzhenjun
* @date 2023/10/31 14:40
*/
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/stock")
@RestController
public class StockController {
private final RemoteMessageFeignService remoteMessageFeignService;
@GetMapping(value = "/subtractStock")
public String subtractStock(@RequestParam(value = "userId") Integer userId,@RequestParam(value = "productId") Integer productId, @RequestParam(value = "num") Integer num) {
log.info("====>庫存模塊<========");
if (productId < 1) {
throw new RuntimeException("商品不存在,請(qǐng)重新請(qǐng)求!");
}
// 調(diào)用短信模塊給用戶發(fā)下單成功短信
String messageResult = remoteMessageFeignService.sendMessage(userId);
log.info("發(fā)送短信結(jié)果:{}", messageResult);
return "扣減庫存成功!";
}
}
RemoteMessageFeignService:
/**
* @author wangzhenjun
* @date 2023/10/31 14:29
*/
@FeignClient(value = "service-message")
public interface RemoteMessageFeignService {
@GetMapping(value = "/message/sendMessage/{userId}")
String sendMessage(@PathVariable(value = "userId") Integer userId);
}
MessageController :
/**
* @author wangzhenjun
* @date 2023/10/31 14:40
*/
@Slf4j
@RequestMapping("/message")
@RestController
public class MessageController {
@GetMapping(value = "/sendMessage/{userId}")
public String sendMessage(@PathVariable(value = "userId") Integer userId) {
log.info("====>短信模塊<========");
if (userId < 1 || userId > 999999) {
throw new RuntimeException("用戶不存在,請(qǐng)重新請(qǐng)求!");
}
return "發(fā)送短信成功!";
}
}
4、啟動(dòng)nacos
Windows下啟動(dòng)nacos,找到nacos下的bin目錄執(zhí)行命令:
startup.cmd -m standalone
找到地址,訪問。用戶名密碼都是nacos。可以在配置文件中修改!
5、注冊(cè)成功
我們把三個(gè)模塊進(jìn)行啟動(dòng)!nacos上已經(jīng)可以看到我們的服務(wù)注冊(cè)上了,可以通過服務(wù)名進(jìn)行調(diào)用了!
五、調(diào)試
我們以訂單模塊為入口進(jìn)行鏈路調(diào)用:
http://localhost:9000/order/createOrder?userId=2&productId=89。
1、查看日志
我們看一下訂單模塊的日志。
可以總結(jié)出Sleuth 日志格式:
[service-order,e36ebe859a7473e7,e36ebe859a7473e7]
[服務(wù)名稱,Trace ID,Span ID]
在最開始的鏈路上,Trace ID 和 Span ID 的值通常是相同的,這是因?yàn)樗鼈兌即砹苏麄€(gè)請(qǐng)求的追蹤。
「一條鏈路使用一個(gè)相同的Trace ID。」
在日志沒有體現(xiàn)出Parent Span ID,不過不應(yīng)該,我們可以通過Zipkin來看鏈路!
2、Zipkin查看
前面我們已經(jīng)進(jìn)入了Zipkin頁面了,只需要刷新一下就可以看到每次鏈路的記錄了!
點(diǎn)擊SHOW按鈕,可以看到詳細(xì)鏈路信息:
耗時(shí),深度,Trance ID 還是挺好的。
3、模擬異常
我們來把商品修改一下:
http://localhost:9000/order/createOrder?userId=2&productId=-89。
此時(shí)是庫存服務(wù)出現(xiàn)的問題,就不會(huì)展示下一個(gè)消息模塊,自然而然的找到了出現(xiàn)問題的鏈路和根源!
在模擬一個(gè)三級(jí)錯(cuò)誤的,就會(huì)看到鏈路的最后一級(jí)!
下面還可以查詢依賴關(guān)系:
點(diǎn)擊節(jié)點(diǎn),可以查看匯總,調(diào)用次數(shù)和失敗次數(shù)的統(tǒng)計(jì)分析!
六、總結(jié)
分布式鏈路追蹤已經(jīng)成為現(xiàn)代微服務(wù)架構(gòu)中不可或缺的工具之一。
通過它,我們可以清晰地跟蹤請(qǐng)求的調(diào)用路徑,了解系統(tǒng)的性能,診斷潛在問題,并不斷優(yōu)化我們的應(yīng)用程序。
Spring Cloud Sleuth讓我們輕松生成和傳播跟蹤信息,使我們的微服務(wù)能夠協(xié)同工作,無縫地捕捉每個(gè)請(qǐng)求的處理路徑。
Zipkin作為一個(gè)流行的分布式追蹤系統(tǒng),為我們提供了可視化界面,使我們能夠以圖形化的方式查看和分析跟蹤數(shù)據(jù)。
「當(dāng)然簡(jiǎn)單系統(tǒng)上這個(gè)大材小用,但是我們可以在項(xiàng)目中試試,加了也不會(huì)影響程序的正常運(yùn)行,做一個(gè)簡(jiǎn)單的知識(shí)儲(chǔ)備!」