男女情感類網(wǎng)站谷歌優(yōu)化技巧
文章目錄
- 一、背景
- 調(diào)用路徑
- 部署環(huán)境
- 問題
- 二、方案
- 三、Demo示例
- 1、MDC
- 2、RequestInterceptor
- 3、HandlerInterceptor
- 4、logback.xml
- 四、后續(xù)改進思路
一、背景
首先這個項目屬于小型項目,由于人手以及時間限制,并未引入Skywalking等中間件來做調(diào)用鏈路追蹤。Skywalking不在此次的討論范圍中。
其次介紹一下項目的相關(guān)背景
調(diào)用路徑
項目中主要有兩種調(diào)用路徑
- Web請求走統(tǒng)一的網(wǎng)關(guān)入口,調(diào)用后端服務(wù)
- XXL-JOB定時任務(wù)執(zhí)行調(diào)度
部署環(huán)境
Kubernetes
問題
走統(tǒng)一網(wǎng)關(guān)入口的請求不用擔(dān)心,在網(wǎng)關(guān)那邊加了TraceID,但是XXL-JOB由于是自動注冊,且部署環(huán)境是在K8S內(nèi),XXL-JOB獲取到的是Pod的IP,網(wǎng)關(guān)并未攔截到。
由于項目的邏輯較為復(fù)雜,XXL-JOB的調(diào)度任務(wù)屬于其中比較重要的一塊,對于前期開發(fā)的調(diào)試以及后期問題的確認(rèn),加上TraceID是非常有必要的。
二、方案
首先確認(rèn)是的XXLJOB執(zhí)行定時任務(wù)時,JobHandler沒有TraceID,不考慮使用中間件的話,就只有兩種方案了。
一種是改造XXL-JOB源碼,在發(fā)起請求中添加TraceID;另一種則是在后端服務(wù)攔截到XXL-JOB的請求,在入口添加TraceID。
XXL-JOB的源碼沒有具體研究過,之前只是做過適配Oracle,改造起來有一定難度,所以最后采用的方案還是在后端服務(wù)攔截請求,添加TraceID。
在網(wǎng)上搜索了一下相關(guān)資料,發(fā)現(xiàn)實現(xiàn)起來還是比較簡單的,一般都是通過spring aop的方式,在Slf4j的MDC中添加TraceID。
在這里簡單介紹下MDC,之前我也沒做過更多了解。
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。某些應(yīng)用程序采用多線程的方式來處理多個用戶的請求。
MDC 可以看成是一個與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問當(dāng)需要記錄日志時,只需要從 MDC 中獲取所需的信息即可。
其實就是使用ThreadLocal來存儲,而由于請求到Java后端服務(wù)時,Tomcat會分配一個線程,直至請求結(jié)束,這樣就會保證我們在入口添加的TraceID,會傳遞到整條鏈路。
但是使用MDC調(diào)用存在兩個問題:
-
子線程中日志TraceID丟失
-
跨服務(wù)調(diào)用日志TraceID丟失
同時項目中使用了Openfeign,在發(fā)起端使用 RequestInterceptor 來攔截,添加TraceID,然后在接收端使用 HandlerInterceptor 攔截。
即最終方案是 MDC+RequestInterceptor+HandlerInterceptor
整體的調(diào)用鏈路如下:
暫時無法在飛書文檔外展示此內(nèi)容
三、Demo示例
1、MDC
@Aspect
@Component
@Slf4j
public class XxlJobAopConfig {@Before("@annotation(com.xxl.job.core.handler.annotation.XxlJob)")public void beforeMethod() {MDC.put('traceId',UUID.randomUUID().toString().toLowerCase());}
}
2、RequestInterceptor
@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {template.header('traceId', MDC.get(HeaderExtraInfoConstants.traceId));}
}
@FeignClient(name = "test", url = "xxx", configuration = FeignRequestInterceptor.class)
3、HandlerInterceptor
@Slf4j
@Component
public class HeaderInterceptor implements HandlerInterceptor {@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception arg3) {MDC.remove('traceId');}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView arg3) {}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String traceId = request.getHeader(HeaderExtraInfoConstants.traceId);if (StringUtils.isEmpty(traceId)) {MDC.put('traceId', UUID.randomUUID().toString().toLowerCase());} else {MDC.put('traceId', traceId);}return true;}}
@Configuration
public class InterceptorConfiguration extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new HeaderInterceptor()).addPathPatterns("/**");}
}
4、logback.xml
%d{yyyy-MM-dd HH:mm:ss.SSS} ---> [%X{traceId}] ---> [%thread] ---> %-5level %logger{50} - %msg%n
四、后續(xù)改進思路
上述方案有較大的局限性,只適用于服務(wù)間通過feign調(diào)用的方式,如果有其他如okhttp的方式,需要再添加攔截器。對于多線程的問題也并未解決,常見的方式是通過重寫線程池來解決。
-
豐富調(diào)用場景,添加攔截器
-
重寫線程池
-
由于部署在K8S集群,可啟用Istio進行服務(wù)治理