這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)微服務(wù)如何實(shí)現(xiàn)簡(jiǎn)單的分布式日志追蹤,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
在南雄等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì) 網(wǎng)站設(shè)計(jì)制作按需定制制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),成都全網(wǎng)營(yíng)銷推廣,成都外貿(mào)網(wǎng)站制作,南雄網(wǎng)站建設(shè)費(fèi)用合理。
最近想給項(xiàng)目添加一個(gè)簡(jiǎn)單的分布式請(qǐng)求跟蹤功能,從前端發(fā)起請(qǐng)求到網(wǎng)關(guān),再?gòu)木W(wǎng)關(guān)調(diào)用 Spring Cloud 的微服務(wù),這些過(guò)程中希望能從日志中看到一個(gè)分布式 ID 的鏈路,通過(guò)請(qǐng)求的 ID 可以追蹤整一條鏈路,方便問(wèn)題的排查。
現(xiàn)成的方案自然是使用 SkyWalking 、 Spring Cloud Sleuth 、Zipkin 之類的組件,但是想到主要的目的記錄一個(gè)可以一直貫通各個(gè)服務(wù)的 ID,方便日志查詢,也就不想引入太多復(fù)雜的組件,最終決定通過(guò) MDC 在日志中輸出追蹤的 ID,然后在 Feign 和 RestTemplate 中將請(qǐng)求 ID 在微服務(wù)中傳遞。
主要包括幾個(gè)步驟:
從前端生成請(qǐng)求 ID 并加入請(qǐng)求頭帶入網(wǎng)關(guān)
網(wǎng)關(guān)通過(guò) WebFilter 攔截并加入 MDC 中,在 log 中輸出
在 Feign 和 RequestTemplate 中將請(qǐng)求 ID 在帶到 HTTP 的 Header 中微服務(wù)傳遞
各個(gè)微服務(wù)同樣通過(guò) WebFilter 實(shí)現(xiàn)攔截并加入 MDC,在 log 中輸出
MDC
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 Log4j 和 Logback 提供的一種方便在多線程條件下記錄日志的功能。 MDC 可以看成是一個(gè)與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對(duì)。
MDC 的關(guān)鍵操作:
向 MDC 中設(shè)置值:MDC.put(key, value);
從 MDC 中取值:MDC.get(key);
將 MDC 中內(nèi)容打印到日志中:%X{key}
新增 TraceId 工具類
先新增一個(gè) TraceIdUtils 工具類,用于定義 TRACE_ID 的常量值以及設(shè)置及生成 TRACE_ID 的方法,后續(xù)代碼中都是通過(guò)這個(gè)估計(jì)類進(jìn)行操作。
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.MDC; public class TraceIdUtils { public static final String TRACE_ID = "traceId"; private static final int MAX_ID_LENGTH = 10; /** * 生成 traceId */ private static String genTraceId() { return RandomStringUtils.randomAlphanumeric(MAX_ID_LENGTH); } /** * 設(shè)置 traceId */ public static void setTraceId(String traceId) { // 如果參數(shù)為空,則生成新 ID traceId = StringUtils.isBlank(traceId) ? genTraceId() : traceId; // 將 traceId 放到 MDC 中 MDC.put(TRACE_ID, StringUtils.substring(traceId, -MAX_ID_LENGTH)); } /** * 獲取 traceId */ public static String getTraceId() { // 獲取 String traceId = MDC.get(TRACE_ID); // 如果 traceId 為空,則生成新 ID return StringUtils.isBlank(traceId) ? genTraceId() : traceId; } }
通過(guò) WebFilter 添加 TraceId 過(guò)濾器
新增一個(gè) GenericFilterBean ,從請(qǐng)求頭中獲取 TraceIdUtils.TRACE_ID 對(duì)應(yīng)的值,該值在前端發(fā)起請(qǐng)求或者微服務(wù)之間傳遞都會(huì)帶上,如果沒(méi)有,則 TraceIdUtils.setTraceId 會(huì)生成一個(gè)。
import org.springframework.core.annotation.Order; import org.springframework.web.filter.GenericFilterBean; @WebFilter(urlPatterns = "/*", filterName = "traceIdFilter") @Order(1) public class TraceIdFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { // traceId初始化 HttpServletRequest req = (HttpServletRequest) request; String traceId = req.getHeader(TraceIdUtils.TRACE_ID); TraceIdUtils.setTraceId(traceId); // 執(zhí)行后續(xù)過(guò)濾器 filterChain.doFilter(request, response); } }
不要忘記在 SpringBoot 的啟動(dòng)類加上 @ServletComponentScan 注解,否則自定義的 Filter 無(wú)法生效。其中 “com.yourtion.trace.filter” 是 TraceIdFilter 所在的包名。
@ServletComponentScan(basePackages = "com.yourtion.trace.filter") @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
在 Feign 上添加 TraceId
因?yàn)?@FeignClient 的代理類在執(zhí)行的時(shí)候,會(huì)去使用使用到 Spring 上下文的 RequestInterceptor,所以自定義自己的攔截器,然后注入到 Spring 上下文中,這樣就可以在請(qǐng)求的上下文中添加自定義的請(qǐng)求頭。
import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.stereotype.Service; @Service public class FeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header(TraceIdUtils.TRACE_ID, TraceIdUtils.getTraceId()); } }
在 RestTemplate 上添加 TraceId
還有一部分請(qǐng)求是通過(guò) RestTemplate 發(fā)起的,之前我們是自己實(shí)現(xiàn)了 RestTemplateConfig 的配置類,這次在相關(guān)的配置上添加:
RestTemplate restTemplate = builder.additionalInterceptors((request, body, execution) -> { request.getHeaders().add(TraceIdUtils.TRACE_ID, TraceIdUtils.getTraceId()); return execution.execute(request, body); }).build();
至此,鏈路上的 TraceId 添加已經(jīng)完成,剩下的就是在日志中打印出來(lái)了。
修改 Log4j2 的 layout 格式
修改日志的layout格式,將MDC中的traceId打印出來(lái):
)}%n"/> )}%n"/>
上述就是小編為大家分享的微服務(wù)如何實(shí)現(xiàn)簡(jiǎn)單的分布式日志追蹤了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。