新聞中心
在《SpringCloud Alibaba實(shí)戰(zhàn)》專欄前面的文章中,我們實(shí)現(xiàn)了用戶微服務(wù)、商品微服務(wù)和訂單微服務(wù)之間的遠(yuǎn)程調(diào)用,并且實(shí)現(xiàn)了服務(wù)調(diào)用的負(fù)載均衡。也基于阿里開源的Sentinel實(shí)現(xiàn)了服務(wù)的限流與容錯(cuò),并詳細(xì)介紹了Sentinel的核心技術(shù)與配置規(guī)則。

簡(jiǎn)單介紹了服務(wù)網(wǎng)關(guān),并對(duì)SpringCloud Gateway的核心架構(gòu)進(jìn)行了簡(jiǎn)要說(shuō)明,也在項(xiàng)目中整合了SpringCloud Gateway網(wǎng)關(guān)實(shí)現(xiàn)了通過網(wǎng)關(guān)訪問后端微服務(wù),同時(shí),也基于SpringCloud Gateway整合Sentinel實(shí)現(xiàn)了網(wǎng)關(guān)的限流功能,詳細(xì)介紹了SpringCloud Gateway網(wǎng)關(guān)的核心技術(shù)。
在鏈路追蹤章節(jié),我們開始簡(jiǎn)單介紹了分布式鏈路追蹤技術(shù)與解決方案。
今天,正式進(jìn)入鏈路追蹤章節(jié),本章,就正式在項(xiàng)目中整合Sleuth實(shí)現(xiàn)鏈路追蹤。
本章總覽
Sleuth概述
Sleuth是SpringCloud中提供的一個(gè)分布式鏈路追蹤組件,在設(shè)計(jì)上大量參考并借用了Google Dapper的設(shè)計(jì)。
Span簡(jiǎn)介
Span在Sleuth中代表一組基本的工作單元,當(dāng)請(qǐng)求到達(dá)各個(gè)微服務(wù)時(shí),Sleuth會(huì)通過一個(gè)唯一的標(biāo)識(shí),也就是SpanId來(lái)標(biāo)記開始通過這個(gè)微服務(wù),在當(dāng)前微服務(wù)中執(zhí)行的具體過程和執(zhí)行結(jié)束。
此時(shí),通過SpanId標(biāo)記的開始時(shí)間戳和結(jié)束時(shí)間戳,就能夠統(tǒng)計(jì)到當(dāng)前Span的調(diào)用時(shí)間,也就是當(dāng)前微服務(wù)的執(zhí)行時(shí)間。另外,也可以用過Span獲取到事件的名稱,請(qǐng)求的信息等數(shù)據(jù)。
總結(jié):遠(yuǎn)程調(diào)用和Span是一對(duì)一的關(guān)系,是分布式鏈路追蹤中最基本的工作單元,每次發(fā)送一個(gè)遠(yuǎn)程調(diào)用服務(wù)就會(huì)產(chǎn)生一個(gè) Span。Span Id 是一個(gè) 64 位的唯一 ID,通過計(jì)算 Span 的開始和結(jié)束時(shí)間,就可以統(tǒng)計(jì)每個(gè)服務(wù)調(diào)用所耗費(fèi)的時(shí)間。
Trace簡(jiǎn)介
Trace的粒度比Span的粒度大,Trace主要是由具有一組相同的Trace ID的Span組成的,從請(qǐng)求進(jìn)入分布式系統(tǒng)入口經(jīng)過調(diào)用各個(gè)微服務(wù)直到返回的整個(gè)過程,都是一個(gè)Trace。
也就是說(shuō),當(dāng)請(qǐng)求到達(dá)分布式系統(tǒng)的入口時(shí),Sleuth會(huì)為請(qǐng)求創(chuàng)建一個(gè)唯一標(biāo)識(shí),這個(gè)唯一標(biāo)識(shí)就是Trace Id,不管這個(gè)請(qǐng)求在分布式系統(tǒng)中如何流轉(zhuǎn),也不管這個(gè)請(qǐng)求在分布式系統(tǒng)中調(diào)用了多少個(gè)微服務(wù),這個(gè)Trace Id都是不變的,直到整個(gè)請(qǐng)求返回。
總結(jié):一個(gè) Trace 可以對(duì)應(yīng)多個(gè) Span,Trace和Span是一對(duì)多的關(guān)系。Trace Id是一個(gè)64 位的唯一ID。Trace Id可以將進(jìn)入分布式系統(tǒng)入口經(jīng)過調(diào)用各個(gè)微服務(wù),再到返回的整個(gè)過程的請(qǐng)求串聯(lián)起來(lái),內(nèi)部每通過一次微服務(wù)時(shí),都會(huì)生成一個(gè)新的SpanId。Trace串聯(lián)了整個(gè)請(qǐng)求鏈路,而Span在請(qǐng)求鏈路中區(qū)分了每個(gè)微服務(wù)。
Annotation簡(jiǎn)介
Annotation記錄了一段時(shí)間內(nèi)的事件,內(nèi)部使用的重要注解如下所示。
- cs(Client Send)客戶端發(fā)出請(qǐng)求,標(biāo)記整個(gè)請(qǐng)求的開始時(shí)間。
- sr(Server Received)服務(wù)端收到請(qǐng)求開始進(jìn)行處理,通過sr與cs可以計(jì)算網(wǎng)絡(luò)的延遲時(shí)間,例如:sr- cs = 網(wǎng)絡(luò)延遲(服務(wù)調(diào)用的時(shí)間)。
- ss(Server Send)服務(wù)端處理完畢準(zhǔn)備將結(jié)果返回給客戶端, 通過ss與sr可以計(jì)算服務(wù)器上的請(qǐng)求處理時(shí)間,例如:ss - sr = 服務(wù)器上的請(qǐng)求處理時(shí)間。
- cr(Client Reveived)客戶端收到服務(wù)端的響應(yīng),請(qǐng)求結(jié)束。通過cr與cs可以計(jì)算請(qǐng)求的總時(shí)間,例如:cr - cs = 請(qǐng)求的總時(shí)間。
總結(jié):鏈路追蹤系統(tǒng)內(nèi)部定義了少量核心注解,用來(lái)定義一個(gè)請(qǐng)求的開始和結(jié)束,通過這些注解,我們可以計(jì)算出請(qǐng)求的每個(gè)階段的時(shí)間。需要注解的是,這里說(shuō)的請(qǐng)求,是在系統(tǒng)內(nèi)部流轉(zhuǎn)的請(qǐng)求,而不是從瀏覽器、APP、H5、小程序等發(fā)出的請(qǐng)求。
項(xiàng)目整合Sleuth
Sleuth提供了分布式鏈路追蹤能力,如果需要使用Sleuth的鏈路追蹤功能,需要在項(xiàng)目中集成Sleuth。
最簡(jiǎn)使用
(1)在每個(gè)微服務(wù)(用戶微服務(wù)shop-user、商品微服務(wù)shop-product、訂單微服務(wù)shop-order、網(wǎng)關(guān)服務(wù)shop-gateway)下的pom.xml文件中添加如下Sleuth的依賴。
org.springframework.cloud
spring-cloud-starter-sleuth
(2)將項(xiàng)目的application.yml文件備份成application-pre-filter.yml,并將application.yml文件的內(nèi)容替換為application-sentinel.yml文件的內(nèi)容,這一步是為了讓整個(gè)項(xiàng)目集成Sentinel、SpringCloud Gateway和Nacos。application.yml替換后的文件內(nèi)容如下所示。
server:
port: 10002
spring:
application:
name: server-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
port: 7777
dashboard: 127.0.0.1:8888
web-context-unify: false
eager: true
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowCredentials: true
allowedHeaders: "*"
discovery:
locator:
enabled: true
route-id-prefix: gateway-
(3)分別啟動(dòng)Nacos、Sentinel、用戶微服務(wù)shop-user,商品微服務(wù)shop-product,訂單微服務(wù)shop-order和網(wǎng)關(guān)服務(wù)shop-gateway,在瀏覽器中輸入鏈接localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。
(4)分別查看用戶微服務(wù)shop-user,商品微服務(wù)shop-product,訂單微服務(wù)shop-order和網(wǎng)關(guān)服務(wù)shop-gateway的控制臺(tái)輸出,每個(gè)服務(wù)的控制臺(tái)都輸出了如下格式所示的信息。
[微服務(wù)名稱,TraceID,SpanID,是否將結(jié)果輸出到第三方平臺(tái)]
具體如下所示。
- 用戶微服務(wù)shop-user。
[server-user,03fef3d312450828,76b298dba54ec579,true]
- 商品微服務(wù)shop-product。
[server-product,03fef3d312450828,41ac8836d2df4eec,true]
[server-product,03fef3d312450828,6b7b3662d63372bf,true]
- 訂單微服務(wù)shop-order。
[server-order,03fef3d312450828,cbd935d57cae84f9,true]
- 網(wǎng)關(guān)服務(wù)shop-gateway。
[server-gateway,03fef3d312450828,03fef3d312450828,true]
可以看到,每個(gè)服務(wù)都打印出了鏈路追蹤的日志信息,說(shuō)明引入Sleuth的依賴后,就可以在命令行查看鏈路追蹤情況。
抽樣采集數(shù)據(jù)
Sleuth支持抽樣采集數(shù)據(jù)。尤其是在高并發(fā)場(chǎng)景下,如果采集所有的數(shù)據(jù),那么采集的數(shù)據(jù)量就太大了,非常耗費(fèi)系統(tǒng)的性能。通常的做法是可以減少一部分?jǐn)?shù)據(jù)量,特別是對(duì)于采用Http方式去發(fā)送采集數(shù)據(jù),能夠提升很大的性能。
Sleuth可以采用如下方式配置抽樣采集數(shù)據(jù)。
spring:
sleuth:
sampler:
probability: 1.0
追蹤自定義線程池
Sleuth支持對(duì)異步任務(wù)的鏈路追蹤,在項(xiàng)目中使用@Async注解開啟一個(gè)異步任務(wù)后,Sleuth會(huì)為異步任務(wù)重新生成一個(gè)Span。但是如果使用了自定義的異步任務(wù)線程池,則會(huì)導(dǎo)致Sleuth無(wú)法新創(chuàng)建一個(gè)Span,而是會(huì)重新生成Trace和Span。此時(shí),需要使用Sleuth提供的LazyTraceExecutor類來(lái)包裝下異步任務(wù)線程池,才能在異步任務(wù)調(diào)用鏈路中重新創(chuàng)建Span。
在服務(wù)中開啟異步線程池任務(wù),需要使用@EnableAsync。所以,在演示示例前,先在用戶微服務(wù)shop-user的io.binghe.shop.UserStarter啟動(dòng)類上添加@EnableAsync注解,如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 啟動(dòng)用戶服的類
*/
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.user.mapper" })
@EnableDiscoveryClient
@EnableAsync
public class UserStarter {
public static void main(String[] args){
SpringApplication.run(UserStarter.class, args);
}
}
演示使用@Async注解開啟任務(wù)
(1)在用戶微服務(wù)shop-user的io.binghe.shop.user.service.UserService接口中定義一個(gè)asyncMethod()方法,如下所示。
void asyncMethod();
(2)在用戶微服務(wù)shop-user的io.binghe.shop.user.service.impl.UserServiceImpl類中實(shí)現(xiàn)asyncMethod()方法,并在asyncMethod()方法上添加@Async注解,如下所示。
@Async
@Override
public void asyncMethod() {
log.info("執(zhí)行了異步任務(wù)...");
}
(3)在用戶微服務(wù)shop-user的io.binghe.shop.user.controller.UserController類中新增asyncApi()方法,如下所示。
@GetMapping(value = "/async/api")
public String asyncApi() {
log.info("執(zhí)行異步任務(wù)開始...");
userService.asyncMethod();
log.info("異步任務(wù)執(zhí)行結(jié)束...");
return "asyncApi";
}
(4)分別啟動(dòng)用戶微服務(wù)和網(wǎng)關(guān)服務(wù),在瀏覽器中輸入鏈接http://localhost:10001/server-user/user/async/api
(5)查看用戶微服務(wù)與網(wǎng)關(guān)服務(wù)的控制臺(tái)日志,分別存在如下日志。
- 用戶微服務(wù)。
[server-user,499d6c7128399ed0,a81bd920de0b07de,true]執(zhí)行異步任務(wù)開始...
[server-user,499d6c7128399ed0,a81bd920de0b07de,true]異步任務(wù)執(zhí)行結(jié)束...
[server-user,499d6c7128399ed0,e2f297d512f40bb8,true]執(zhí)行了異步任務(wù)...
- 網(wǎng)關(guān)服務(wù)。
[server-gateway,499d6c7128399ed0,499d6c7128399ed0,true]
可以看到Sleuth為異步任務(wù)重新生成了Span。
演示自定義任務(wù)線程池
在演示使用@Async注解開啟任務(wù)的基礎(chǔ)上繼續(xù)演示自定義任務(wù)線程池,驗(yàn)證Sleuth是否為自定義線程池新創(chuàng)建了Span。
(1)在用戶微服務(wù)shop-user中新建io.binghe.shop.user.config包,在包下創(chuàng)建ThreadPoolTaskExecutorConfig類,繼承org.springframework.scheduling.annotation.AsyncConfigurerSupport類,用來(lái)自定義異步任務(wù)線程池,代碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description Sleuth異步線程池配置
*/
@Configuration
@EnableAutoConfiguration
public class ThreadPoolTaskExecutorConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("trace-thread-");
executor.initialize();
return executor;
}
}
(2)以debug的形式啟動(dòng)用戶微服務(wù)和網(wǎng)關(guān)服務(wù),并在io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法中打上斷點(diǎn),如下所示。
可以看到,項(xiàng)目啟動(dòng)后并沒有進(jìn)入io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法,說(shuō)明項(xiàng)目啟動(dòng)時(shí),并不會(huì)創(chuàng)建異步任務(wù)線程池。
(3)在瀏覽器中輸入鏈接http://localhost:10001/server-user/user/async/api,此時(shí)可以看到程序已經(jīng)執(zhí)行到io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法的斷點(diǎn)位置。
說(shuō)明異步任務(wù)線程池是在調(diào)用了異步任務(wù)的時(shí)候創(chuàng)建的。
接下來(lái),按F8跳過斷點(diǎn)繼續(xù)運(yùn)行程序,可以看到瀏覽器上的顯示結(jié)果如下。
(4)查看用戶微服務(wù)與網(wǎng)關(guān)服務(wù)的控制臺(tái)日志,分別存在如下日志。
- 用戶微服務(wù)。
[server-user,f89f2355ec3f9df1,4d679555674e96a4,true]執(zhí)行異步任務(wù)開始...
[server-user,f89f2355ec3f9df1,4d679555674e96a4,true]異步任務(wù)執(zhí)行結(jié)束...
[server-user,0ee48d47e58e2a42,0ee48d47e58e2a42,true]執(zhí)行了異步任務(wù)...
- 網(wǎng)關(guān)服務(wù)。
[server-gateway,f89f2355ec3f9df1,f89f2355ec3f9df1,true]
可以看到,使用自定義異步任務(wù)線程池時(shí),在用戶微服務(wù)中在執(zhí)行異步任務(wù)時(shí),重新生成了Trace和Span。
注意對(duì)比用戶微服務(wù)中輸出的三條日志信息,最后一條日志信息的TraceID和SpanID與前兩條日志都不同。
演示包裝自定義線程池
在自定義任務(wù)線程池的基礎(chǔ)上繼續(xù)演示包裝自定義線程池,驗(yàn)證Sleuth是否為包裝后的自定義線程池新創(chuàng)建了Span。
(1)在用戶微服務(wù)shop-user的io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig類中注入BeanFactory,并在getAsyncExecutor()方法中使用org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor()來(lái)包裝返回的異步任務(wù)線程池,修改后的io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig類的代碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description Sleuth異步線程池配置
*/
@Configuration
@EnableAutoConfiguration
public class ThreadPoolTaskExecutorConfig extends AsyncConfigurerSupport {
@Autowired
private BeanFactory beanFactory;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("trace-thread-");
executor.initialize();
return new LazyTraceExecutor(this.beanFactory, executor);
}
}
(2)分別啟動(dòng)用戶微服務(wù)和網(wǎng)關(guān)服務(wù),在瀏覽器中輸入鏈接http://localhost:10001/server-user/user/async/api
(3)查看用戶微服務(wù)與網(wǎng)關(guān)服務(wù)的控制臺(tái)日志,分別存在如下日志。
- 用戶微服務(wù)、
[server-user,157891cb90fddb65,0a278842776b1f01,true]執(zhí)行異步任務(wù)開始...
[server-user,157891cb90fddb65,0a278842776b1f01,true]異步任務(wù)執(zhí)行結(jié)束...
[server-user,157891cb90fddb65,1ba55fd3432b77ae,true]執(zhí)行了異步任務(wù)...
- 網(wǎng)關(guān)服務(wù)。
[server-gateway,157891cb90fddb65,157891cb90fddb65,true]
可以看到Sleuth為異步任務(wù)重新生成了Span。
綜上說(shuō)明:Sleuth支持對(duì)異步任務(wù)的鏈路追蹤,在項(xiàng)目中使用@Async注解開啟一個(gè)異步任務(wù)后,Sleuth會(huì)為異步任務(wù)重新生成一個(gè)Span。但是如果使用了自定義的異步任務(wù)線程池,則會(huì)導(dǎo)致Sleuth無(wú)法新創(chuàng)建一個(gè)Span,而是會(huì)重新生成Trace和Span。此時(shí),需要使用Sleuth提供的LazyTraceExecutor類來(lái)包裝下異步任務(wù)線程池,才能在異步任務(wù)調(diào)用鏈路中重新創(chuàng)建Span。
自定義鏈路過濾器
在Sleuth中存在鏈路過濾器,并且還支持自定義鏈路過濾器。
自定義鏈路過濾器概述
TracingFilter是Sleuth中負(fù)責(zé)處理請(qǐng)求和響應(yīng)的組件,可以通過注冊(cè)自定義的TracingFilter實(shí)例來(lái)實(shí)現(xiàn)一些擴(kuò)展性的需求。
演示自定義鏈路過濾器
本案例演示通過過濾器驗(yàn)證只有HTTP或者HTTPS請(qǐng)求才能訪問接口,并且在訪問的鏈接不是靜態(tài)文件時(shí),將traceId放入HttpRequest中在服務(wù)端獲取,并在響應(yīng)結(jié)果中添加自定義Header,名稱為SLEUTH-HEADER,值為traceId。
(1)在用戶微服務(wù)shop-user中新建io.binghe.shop.user.filter包,并創(chuàng)建MyGenericFilter類,繼承org.springframework.web.filter.GenericFilterBean類,代碼如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 鏈路過濾器
*/
@Component
@Order( Ordered.HIGHEST_PRECEDENCE + 6)
public class MyGenericFilter extends GenericFilterBean{
private Pattern skipPattern = Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN);
private final Tracer tracer;
public MyGenericFilter(Tracer tracer){
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)){
throw new ServletException("只支持HTTP訪問");
}
Span currentSpan = this.tracer.currentSpan();
if (currentSpan == null) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = ((HttpServletResponse) response);
boolean skipFlag = skipPattern.matcher(httpServletRequest.getRequestURI()).matches();
if (!skipFlag){
String traceId = currentSpan.context().traceIdString();
httpServletRequest.setAttribute("traceId", traceId);
httpServletResponse.addHeader("SLEUTH-HEADER", traceId);
}
chain.doFilter(httpServletRequest, httpServletResponse);
}
}
(2)在用戶微服務(wù)shop-user的io.binghe.shop.user.controller.UserController類中新建sleuthFilter()方法,在sleuthFilter()方法中獲取并打印traceId,如下所示。
@GetMapping(value = "/sleuth/filter/api")
public String sleuthFilter(HttpServletRequest request) {
Object traceIdObj = request.getAttribute("traceId");
String traceId = traceIdObj == null ? "" : traceIdObj.toString();
log.info("獲取到的traceId為: " + traceId);
return "sleuthFilter";
}
(3)分別啟動(dòng)用戶微服務(wù)和網(wǎng)關(guān)服務(wù),在瀏覽器中輸入http://localhost:10001/server-user/user/sleuth/filter/api,如下所示。
查看用戶微服務(wù)的控制臺(tái)會(huì)輸出如下信息。
獲取到的traceId為: f63ae7702f6f4bba
查看瀏覽器的控制臺(tái),看到在響應(yīng)的結(jié)果信息中新增了一個(gè)名稱為SLEUTH-HEADER,值為f63ae7702f6f4bba的Header,如下所示。
說(shuō)明使用Sleuth的過濾器可以處理請(qǐng)求和響應(yīng)信息,并且可以在Sleuth的過濾器中獲取到TraceID。
分享文章:鏈路追蹤:項(xiàng)目整合Sleuth實(shí)現(xiàn)鏈路追蹤
文章鏈接:http://m.fisionsoft.com.cn/article/cdsdced.html


咨詢
建站咨詢
