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

簡單介紹了服務(wù)網(wǎng)關(guān),并對SpringCloud Gateway的核心架構(gòu)進行了簡要說明,也在項目中整合了SpringCloud Gateway網(wǎng)關(guān)實現(xiàn)了通過網(wǎng)關(guān)訪問后端微服務(wù),同時,也基于SpringCloud Gateway整合Sentinel實現(xiàn)了網(wǎng)關(guān)的限流功能,詳細介紹了SpringCloud Gateway網(wǎng)關(guān)的核心技術(shù)。
在鏈路追蹤章節(jié),我們開始簡單介紹了分布式鏈路追蹤技術(shù)與解決方案。
今天,正式進入鏈路追蹤章節(jié),本章,就正式在項目中整合Sleuth實現(xiàn)鏈路追蹤。
本章總覽
Sleuth概述
Sleuth是SpringCloud中提供的一個分布式鏈路追蹤組件,在設(shè)計上大量參考并借用了Google Dapper的設(shè)計。
Span簡介
Span在Sleuth中代表一組基本的工作單元,當請求到達各個微服務(wù)時,Sleuth會通過一個唯一的標識,也就是SpanId來標記開始通過這個微服務(wù),在當前微服務(wù)中執(zhí)行的具體過程和執(zhí)行結(jié)束。
此時,通過SpanId標記的開始時間戳和結(jié)束時間戳,就能夠統(tǒng)計到當前Span的調(diào)用時間,也就是當前微服務(wù)的執(zhí)行時間。另外,也可以用過Span獲取到事件的名稱,請求的信息等數(shù)據(jù)。
總結(jié):遠程調(diào)用和Span是一對一的關(guān)系,是分布式鏈路追蹤中最基本的工作單元,每次發(fā)送一個遠程調(diào)用服務(wù)就會產(chǎn)生一個 Span。Span Id 是一個 64 位的唯一 ID,通過計算 Span 的開始和結(jié)束時間,就可以統(tǒng)計每個服務(wù)調(diào)用所耗費的時間。
Trace簡介
Trace的粒度比Span的粒度大,Trace主要是由具有一組相同的Trace ID的Span組成的,從請求進入分布式系統(tǒng)入口經(jīng)過調(diào)用各個微服務(wù)直到返回的整個過程,都是一個Trace。
也就是說,當請求到達分布式系統(tǒng)的入口時,Sleuth會為請求創(chuàng)建一個唯一標識,這個唯一標識就是Trace Id,不管這個請求在分布式系統(tǒng)中如何流轉(zhuǎn),也不管這個請求在分布式系統(tǒng)中調(diào)用了多少個微服務(wù),這個Trace Id都是不變的,直到整個請求返回。
總結(jié):一個 Trace 可以對應(yīng)多個 Span,Trace和Span是一對多的關(guān)系。Trace Id是一個64 位的唯一ID。Trace Id可以將進入分布式系統(tǒng)入口經(jīng)過調(diào)用各個微服務(wù),再到返回的整個過程的請求串聯(lián)起來,內(nèi)部每通過一次微服務(wù)時,都會生成一個新的SpanId。Trace串聯(lián)了整個請求鏈路,而Span在請求鏈路中區(qū)分了每個微服務(wù)。
Annotation簡介
Annotation記錄了一段時間內(nèi)的事件,內(nèi)部使用的重要注解如下所示。
- cs(Client Send)客戶端發(fā)出請求,標記整個請求的開始時間。
- sr(Server Received)服務(wù)端收到請求開始進行處理,通過sr與cs可以計算網(wǎng)絡(luò)的延遲時間,例如:sr- cs = 網(wǎng)絡(luò)延遲(服務(wù)調(diào)用的時間)。
- ss(Server Send)服務(wù)端處理完畢準備將結(jié)果返回給客戶端, 通過ss與sr可以計算服務(wù)器上的請求處理時間,例如:ss - sr = 服務(wù)器上的請求處理時間。
- cr(Client Reveived)客戶端收到服務(wù)端的響應(yīng),請求結(jié)束。通過cr與cs可以計算請求的總時間,例如:cr - cs = 請求的總時間。
總結(jié):鏈路追蹤系統(tǒng)內(nèi)部定義了少量核心注解,用來定義一個請求的開始和結(jié)束,通過這些注解,我們可以計算出請求的每個階段的時間。需要注解的是,這里說的請求,是在系統(tǒng)內(nèi)部流轉(zhuǎn)的請求,而不是從瀏覽器、APP、H5、小程序等發(fā)出的請求。
項目整合Sleuth
Sleuth提供了分布式鏈路追蹤能力,如果需要使用Sleuth的鏈路追蹤功能,需要在項目中集成Sleuth。
最簡使用
(1)在每個微服務(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)將項目的application.yml文件備份成application-pre-filter.yml,并將application.yml文件的內(nèi)容替換為application-sentinel.yml文件的內(nèi)容,這一步是為了讓整個項目集成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)分別啟動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的控制臺輸出,每個服務(wù)的控制臺都輸出了如下格式所示的信息。
[微服務(wù)名稱,TraceID,SpanID,是否將結(jié)果輸出到第三方平臺]
具體如下所示。
- 用戶微服務(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]
可以看到,每個服務(wù)都打印出了鏈路追蹤的日志信息,說明引入Sleuth的依賴后,就可以在命令行查看鏈路追蹤情況。
抽樣采集數(shù)據(jù)
Sleuth支持抽樣采集數(shù)據(jù)。尤其是在高并發(fā)場景下,如果采集所有的數(shù)據(jù),那么采集的數(shù)據(jù)量就太大了,非常耗費系統(tǒng)的性能。通常的做法是可以減少一部分數(shù)據(jù)量,特別是對于采用Http方式去發(fā)送采集數(shù)據(jù),能夠提升很大的性能。
Sleuth可以采用如下方式配置抽樣采集數(shù)據(jù)。
spring:
sleuth:
sampler:
probability: 1.0
追蹤自定義線程池
Sleuth支持對異步任務(wù)的鏈路追蹤,在項目中使用@Async注解開啟一個異步任務(wù)后,Sleuth會為異步任務(wù)重新生成一個Span。但是如果使用了自定義的異步任務(wù)線程池,則會導(dǎo)致Sleuth無法新創(chuàng)建一個Span,而是會重新生成Trace和Span。此時,需要使用Sleuth提供的LazyTraceExecutor類來包裝下異步任務(wù)線程池,才能在異步任務(wù)調(diào)用鏈路中重新創(chuàng)建Span。
在服務(wù)中開啟異步線程池任務(wù),需要使用@EnableAsync。所以,在演示示例前,先在用戶微服務(wù)shop-user的io.binghe.shop.UserStarter啟動類上添加@EnableAsync注解,如下所示。
/**
* @author binghe
* @version 1.0.0
* @description 啟動用戶服的類
*/
@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接口中定義一個asyncMethod()方法,如下所示。
void asyncMethod();
(2)在用戶微服務(wù)shop-user的io.binghe.shop.user.service.impl.UserServiceImpl類中實現(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)分別啟動用戶微服務(wù)和網(wǎng)關(guān)服務(wù),在瀏覽器中輸入鏈接http://localhost:10001/server-user/user/async/api
(5)查看用戶微服務(wù)與網(wǎng)關(guān)服務(wù)的控制臺日志,分別存在如下日志。
- 用戶微服務(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ù)線程池,驗證Sleuth是否為自定義線程池新創(chuàng)建了Span。
(1)在用戶微服務(wù)shop-user中新建io.binghe.shop.user.config包,在包下創(chuàng)建ThreadPoolTaskExecutorConfig類,繼承org.springframework.scheduling.annotation.AsyncConfigurerSupport類,用來自定義異步任務(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的形式啟動用戶微服務(wù)和網(wǎng)關(guān)服務(wù),并在io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法中打上斷點,如下所示。
可以看到,項目啟動后并沒有進入io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法,說明項目啟動時,并不會創(chuàng)建異步任務(wù)線程池。
(3)在瀏覽器中輸入鏈接http://localhost:10001/server-user/user/async/api,此時可以看到程序已經(jīng)執(zhí)行到io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig#getAsyncExecutor()方法的斷點位置。
說明異步任務(wù)線程池是在調(diào)用了異步任務(wù)的時候創(chuàng)建的。
接下來,按F8跳過斷點繼續(xù)運行程序,可以看到瀏覽器上的顯示結(jié)果如下。
(4)查看用戶微服務(wù)與網(wǎng)關(guān)服務(wù)的控制臺日志,分別存在如下日志。
- 用戶微服務(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ù)線程池時,在用戶微服務(wù)中在執(zhí)行異步任務(wù)時,重新生成了Trace和Span。
注意對比用戶微服務(wù)中輸出的三條日志信息,最后一條日志信息的TraceID和SpanID與前兩條日志都不同。
演示包裝自定義線程池
在自定義任務(wù)線程池的基礎(chǔ)上繼續(xù)演示包裝自定義線程池,驗證Sleuth是否為包裝后的自定義線程池新創(chuàng)建了Span。
(1)在用戶微服務(wù)shop-user的io.binghe.shop.user.config.ThreadPoolTaskExecutorConfig類中注入BeanFactory,并在getAsyncExecutor()方法中使用org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor()來包裝返回的異步任務(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)分別啟動用戶微服務(wù)和網(wǎng)關(guān)服務(wù),在瀏覽器中輸入鏈接http://localhost:10001/server-user/user/async/api
(3)查看用戶微服務(wù)與網(wǎng)關(guān)服務(wù)的控制臺日志,分別存在如下日志。
- 用戶微服務(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。
綜上說明:Sleuth支持對異步任務(wù)的鏈路追蹤,在項目中使用@Async注解開啟一個異步任務(wù)后,Sleuth會為異步任務(wù)重新生成一個Span。但是如果使用了自定義的異步任務(wù)線程池,則會導(dǎo)致Sleuth無法新創(chuàng)建一個Span,而是會重新生成Trace和Span。此時,需要使用Sleuth提供的LazyTraceExecutor類來包裝下異步任務(wù)線程池,才能在異步任務(wù)調(diào)用鏈路中重新創(chuàng)建Span。
自定義鏈路過濾器
在Sleuth中存在鏈路過濾器,并且還支持自定義鏈路過濾器。
自定義鏈路過濾器概述
TracingFilter是Sleuth中負責(zé)處理請求和響應(yīng)的組件,可以通過注冊自定義的TracingFilter實例來實現(xiàn)一些擴展性的需求。
演示自定義鏈路過濾器
本案例演示通過過濾器驗證只有HTTP或者HTTPS請求才能訪問接口,并且在訪問的鏈接不是靜態(tài)文件時,將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)分別啟動用戶微服務(wù)和網(wǎng)關(guān)服務(wù),在瀏覽器中輸入http://localhost:10001/server-user/user/sleuth/filter/api,如下所示。
查看用戶微服務(wù)的控制臺會輸出如下信息。
獲取到的traceId為: f63ae7702f6f4bba
查看瀏覽器的控制臺,看到在響應(yīng)的結(jié)果信息中新增了一個名稱為SLEUTH-HEADER,值為f63ae7702f6f4bba的Header,如下所示。
說明使用Sleuth的過濾器可以處理請求和響應(yīng)信息,并且可以在Sleuth的過濾器中獲取到TraceID。
當前題目:鏈路追蹤:項目整合Sleuth實現(xiàn)鏈路追蹤
文章出自:http://m.5511xx.com/article/cdsdced.html


咨詢
建站咨詢
