SpringCloud通過MDC實(shí)現(xiàn)分布式鏈路追蹤
引言
在DDD領(lǐng)域驅(qū)動設(shè)計(jì)中,我們使用SpringCloud來去實(shí)現(xiàn),但排查錯誤的時(shí)候,通常會想到Skywalking,但是引入一個新的服務(wù),增加了系統(tǒng)消耗和管理學(xué)習(xí)成本,對于大型項(xiàng)目比較適合,但是小的項(xiàng)目顯得太過臃腫了,我們此時(shí)就可以使用TraceId,將其存放到MDC中,返回的時(shí)候參數(shù)帶上它,訪問的時(shí)候日志打印出來,每次訪問生成的TraceId不同,這樣可以實(shí)現(xiàn)分布式鏈路追蹤的問題。
通用部分
封裝TraceIdUtil工具類
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)建過濾器
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)建新的請求包裝器 log.info("TraceIdUtil.getTraceId():"+TraceIdUtil.getTraceId()); //將請求和應(yīng)答交給下一個處理器處理 filterChain.doFilter(request,response); }catch (Exception e){ e.printStackTrace(); }finally { //最后移除,不然有可能造成內(nèi)存泄漏 TraceIdUtil.removeTraceId(); } } @Override public void destroy() { log.info("Init Trace filter destroy......."); } }
配置過濾器生效
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ā)送的請求的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)行透傳 if (traceId != null) { template.header(TraceIdUtil.TRACE_ID_KEY,TraceIdUtil.getTraceId()); } } }
統(tǒng)一返回處理
此種情況時(shí)針對BaseResult,,這種統(tǒng)一返回的對象無法直接修改的情況下使用的,如果可以直接修改:
/** * 鏈路追蹤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 { /** * 開關(guān),如果是true才會調(diào)用beforeBodyWrite */ @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @SneakyThrows//異常拋出,相當(dāng)于方法上throw一個異常 @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)建過濾器
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 ); //將請求和應(yīng)答交給下一個處理器處理 filterChain.doFilter(request,response); }catch (Exception e){ e.printStackTrace(); }finally { //最后移除,不然有可能造成內(nèi)存泄漏 TraceIdUtil.removeTraceId(); } } @Override public void destroy() { log.info("Init Trace filter destroy......."); } }
配置過濾器生效
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; } }
線程池
上面對于單線程的情況可以進(jìn)行解決,traceId和Threadlocal很像,是鍵值對模式,會有內(nèi)存溢出問題,還是線程私有的。 所以在多線程的情況下就不能獲取主線程的traceId了。我們就需要設(shè)置線程工廠包裝 Runnable 來解決這個問題。
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通過MDC實(shí)現(xiàn)分布式鏈路追蹤的詳細(xì)內(nèi)容,更多關(guān)于SpringCloud MDC鏈路追蹤的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot 實(shí)現(xiàn)Http接口加簽、驗(yàn)簽操作方法
這篇文章主要介紹了springboot 實(shí)現(xiàn)Http接口加簽、驗(yàn)簽操作,服務(wù)之間接口調(diào)用,通過簽名作為安全認(rèn)證來保證API的安全性,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09JDK的一個Bug監(jiān)聽文件變更的初步實(shí)現(xiàn)思路
這篇文章主要介紹了JDK的一個Bug監(jiān)聽文件變更要小心了,本篇文章就帶大家簡單實(shí)現(xiàn)一個對應(yīng)的功能,并分析一下對應(yīng)的Bug和優(yōu)缺點(diǎn),需要的朋友可以參考下2022-05-05Java集合之Set接口及其實(shí)現(xiàn)類精解
set接口是繼承自Collection的子接口,特點(diǎn)是元素不重復(fù),存儲無序。在set接口的實(shí)現(xiàn)類中添加重復(fù)元素是不會成功的,判斷兩個元素是否重復(fù)根據(jù)元素類重寫的2021-09-09Java對List進(jìn)行排序的兩種實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java對List進(jìn)行排序的兩種實(shí)現(xiàn)方法,第一種是實(shí)體類自己實(shí)現(xiàn)比較,第二種是借助比較器進(jìn)行排序,下面開一起看看詳細(xì)的介紹吧,有需要的朋友們可以參考借鑒。2016-12-12SpringBoot啟動自動終止也不報(bào)錯的原因及解決
這篇文章主要介紹了SpringBoot啟動自動終止也不報(bào)錯的原因及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09Java用BigDecimal類解決Double類型精度丟失的問題
這篇文章主要介紹了Java用BigDecimal類解決Double類型精度丟失的問題,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12Java8的Stream()與ParallelStream()的區(qū)別說明
這篇文章主要介紹了Java8的Stream()與ParallelStream()的區(qū)別說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07