SpringCloud通過(guò)MDC實(shí)現(xiàn)分布式鏈路追蹤
引言
在DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,我們使用SpringCloud來(lái)去實(shí)現(xiàn),但排查錯(cuò)誤的時(shí)候,通常會(huì)想到Skywalking,但是引入一個(gè)新的服務(wù),增加了系統(tǒng)消耗和管理學(xué)習(xí)成本,對(duì)于大型項(xiàng)目比較適合,但是小的項(xiàng)目顯得太過(guò)臃腫了,我們此時(shí)就可以使用TraceId,將其存放到MDC中,返回的時(shí)候參數(shù)帶上它,訪問(wèn)的時(shí)候日志打印出來(lái),每次訪問(wèn)生成的TraceId不同,這樣可以實(shí)現(xiàn)分布式鏈路追蹤的問(wèn)題。
通用部分
封裝TraceIdUtil工具類(lèi)
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import cn.hutool.core.util.IdUtil;
public class TraceIdUtil {
public static final String TRACE_ID_KEY = "TraceId";
/**
* 生成TraceId
* @return
*/
public static String generateTraceId(){
String traceId = IdUtil.fastSimpleUUID().toUpperCase();
MDC.put(TRACE_ID_KEY,traceId);
return traceId;
}
/**
* 生成TraceId
* @return
*/
public static String generateTraceId(String traceId){
if(StringUtils.isBlank(traceId)){
return generateTraceId();
}
MDC.put(TRACE_ID_KEY,traceId);
return traceId;
}
/**
* 獲取TraceId
* @return
*/
public static String getTraceId(){
return MDC.get(TRACE_ID_KEY);
}
/**
* 移除TraceId
* @return
*/
public static void removeTraceId(){
MDC.remove(TRACE_ID_KEY);
}
}
logback.xml日志文件的修改
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [TRACEID:%X{TraceId}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
需注意:

biff 模塊
創(chuàng)建過(guò)濾器
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import com.karry.admin.bff.common.util.TraceIdUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@WebFilter
public class TraceFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("Init Trace filter init.......");
System.out.println("Init Trace filter init.......");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest servletRequest = (HttpServletRequest) request;
String gateWayTraceId = ((HttpServletRequest) request).getHeader(TraceIdUtil.TRACE_ID_KEY);
String traceId = TraceIdUtil.generateTraceId(StringUtils.isEmpty(gateWayTraceId)
? IdUtil.fastSimpleUUID().toUpperCase()
: gateWayTraceId
);
// 創(chuàng)建新的請(qǐng)求包裝器
log.info("TraceIdUtil.getTraceId():"+TraceIdUtil.getTraceId());
//將請(qǐng)求和應(yīng)答交給下一個(gè)處理器處理
filterChain.doFilter(request,response);
}catch (Exception e){
e.printStackTrace();
}finally {
//最后移除,不然有可能造成內(nèi)存泄漏
TraceIdUtil.removeTraceId();
}
}
@Override
public void destroy() {
log.info("Init Trace filter destroy.......");
}
}
配置過(guò)濾器生效
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.karry.admin.bff.common.filter.TraceFilter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class WebConfiguration {
@Bean
@ConditionalOnMissingBean({TraceFilter.class})
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
public FilterRegistrationBean<TraceFilter> traceFilterBean(){
FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new TraceFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
figen接口發(fā)送的head修改
此處修改了發(fā)送的請(qǐng)求的header,在其他模塊就可以獲取從biff層生成的traceId了。
import org.springframework.context.annotation.Configuration;
import com.karry.admin.bff.common.util.TraceIdUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template){
String traceId = TraceIdUtil.getTraceId();
//當(dāng)前線程調(diào)用中有traceId,則將該traceId進(jìn)行透?jìng)?
if (traceId != null) {
template.header(TraceIdUtil.TRACE_ID_KEY,TraceIdUtil.getTraceId());
}
}
}
統(tǒng)一返回處理
此種情況時(shí)針對(duì)BaseResult,,這種統(tǒng)一返回的對(duì)象無(wú)法直接修改的情況下使用的,如果可以直接修改:
/**
* 鏈路追蹤TraceId
*/
public String traceId = TraceIdUtil.getTraceId();
不可以直接修改就用響應(yīng)攔截器進(jìn)行處理:
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import com.karry.app.common.utils.TraceIdUtil;
import com.karry.order.sdk.utils.BeanCopyUtils;
import com.souche.platform.common.model.base.BaseResult;
import lombok.SneakyThrows;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
* 開(kāi)關(guān),如果是true才會(huì)調(diào)用beforeBodyWrite
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows//異常拋出,相當(dāng)于方法上throw一個(gè)異常
@Override
public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
BaseResult result = BeanCopyUtils.copy(object, BaseResult.class);
result.setTraceId(TraceIdUtil.getTraceId());
return result;
}
}
非biff模塊
創(chuàng)建過(guò)濾器
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import com.karry.app.common.utils.TraceIdUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@WebFilter
public class TraceFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("Init Trace filter init.......");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest servletRequest = (HttpServletRequest) request;
String gateWayTraceId = ((HttpServletRequest) request).getHeader(TraceIdUtil.TRACE_ID_KEY);
String traceId = TraceIdUtil.generateTraceId(StringUtils.isEmpty(gateWayTraceId)
? IdUtil.fastSimpleUUID().toUpperCase()
: gateWayTraceId
);
//將請(qǐng)求和應(yīng)答交給下一個(gè)處理器處理
filterChain.doFilter(request,response);
}catch (Exception e){
e.printStackTrace();
}finally {
//最后移除,不然有可能造成內(nèi)存泄漏
TraceIdUtil.removeTraceId();
}
}
@Override
public void destroy() {
log.info("Init Trace filter destroy.......");
}
}
配置過(guò)濾器生效
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.karry.admin.bff.common.filter.TraceFilter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class WebConfiguration {
@Bean
@ConditionalOnMissingBean({TraceFilter.class})
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
public FilterRegistrationBean<TraceFilter> traceFilterBean(){
FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new TraceFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
線程池
上面對(duì)于單線程的情況可以進(jìn)行解決,traceId和Threadlocal很像,是鍵值對(duì)模式,會(huì)有內(nèi)存溢出問(wèn)題,還是線程私有的。 所以在多線程的情況下就不能獲取主線程的traceId了。我們就需要設(shè)置線程工廠包裝 Runnable 來(lái)解決這個(gè)問(wèn)題。
import org.slf4j.MDC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Configuration
public class MyThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
// 自定義 ThreadFactory
ThreadFactory threadFactory = new ThreadFactory() {
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
private final String namePrefix = "Async---";
@Override
public Thread newThread(Runnable r) {
// 獲取主線程的 MDC 上下文
Map<String, String> contextMap = MDC.getCopyOfContextMap();
// 包裝 Runnable 以設(shè)置 MDC 上下文
Runnable wrappedRunnable = () -> {
try {
// 設(shè)置 MDC 上下文
MDC.setContextMap(contextMap);
// 執(zhí)行任務(wù)
r.run();
} finally {
// 清除 MDC 上下文
MDC.clear();
}
};
Thread thread = defaultFactory.newThread(wrappedRunnable);
thread.setName(namePrefix + thread.getName());
return thread;
}
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
10,
30L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500),
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
以上就是SpringCloud通過(guò)MDC實(shí)現(xiàn)分布式鏈路追蹤的詳細(xì)內(nèi)容,更多關(guān)于SpringCloud MDC鏈路追蹤的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java使用Condition實(shí)現(xiàn)精準(zhǔn)喚醒線程詳解
這篇文章主要為大家詳細(xì)介紹了Java如何使用Condition實(shí)現(xiàn)精準(zhǔn)喚醒線程效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-02-02
Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:選擇排序 Selection Sort
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:選擇排序 Selection Sort,本文直接給出實(shí)現(xiàn)代碼,代碼中包含詳細(xì)注釋,需要的朋友可以參考下2015-06-06
如何根據(jù)帶賬號(hào)密碼的WSDL地址生成JAVA代碼
這篇文章主要介紹了如何根據(jù)帶賬號(hào)密碼的WSDL地址生成JAVA代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
使用Maven進(jìn)行依賴(lài)漏洞檢查的實(shí)現(xiàn)指南
在現(xiàn)代軟件開(kāi)發(fā)中,開(kāi)源庫(kù)的使用變得愈加普遍和重要,然而,這些開(kāi)源庫(kù)中的漏洞往往會(huì)成為潛在的安全風(fēng)險(xiǎn),在本文中,我們將探討如何使用 Maven 進(jìn)行依賴(lài)漏洞檢查,以確保項(xiàng)目的安全性和穩(wěn)定性,需要的朋友可以參考下2024-05-05
SpringBoot集成LiteFlow實(shí)現(xiàn)輕量級(jí)工作流引擎的詳細(xì)過(guò)程
LiteFlow 是一款專(zhuān)注于邏輯驅(qū)動(dòng)流程編排的輕量級(jí)框架,它以組件化方式快速構(gòu)建和執(zhí)行業(yè)務(wù)流程,有效解耦復(fù)雜業(yè)務(wù)邏輯,下面給大家介紹SpringBoot集成LiteFlow實(shí)現(xiàn)輕量級(jí)工作流引擎,感興趣的朋友一起看看吧2025-06-06
maven的pom.xml中repositories和distributionManagement使用
這篇文章主要介紹了maven的pom.xml中repositories和distributionManagement使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Spring中@Configuration和@Component注解的區(qū)別及原理
這篇文章主要介紹了Spring中@Configuration和@Component注解的區(qū)別及原理,從功能上來(lái)講,這些注解所負(fù)責(zé)的功能的確不相同,但是從本質(zhì)上來(lái)講,Spring內(nèi)部都將其作為配置注解進(jìn)行處理,需要的朋友可以參考下2023-11-11
Java獲取漢字對(duì)應(yīng)的拼音(全拼或首字母)
這篇文章主要介紹了Java如何獲取漢字對(duì)應(yīng)的拼音(全拼或首字母),文中實(shí)現(xiàn)的方法是引用了pinyin4j-2.5.0.jar,然后給出了完整的示例代碼,有需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-01-01
spring(java,js,html) 截圖上傳圖片實(shí)例詳解
這篇文章主要介紹了spring(java,js,html) 截圖上傳圖片實(shí)例詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07

