男女情感類網(wǎng)站谷歌優(yōu)化技巧
文章目錄
- 一、背景
- 調(diào)用路徑
- 部署環(huán)境
- 問(wèn)題
- 二、方案
- 三、Demo示例
- 1、MDC
- 2、RequestInterceptor
- 3、HandlerInterceptor
- 4、logback.xml
- 四、后續(xù)改進(jìn)思路
一、背景
首先這個(gè)項(xiàng)目屬于小型項(xiàng)目,由于人手以及時(shí)間限制,并未引入Skywalking等中間件來(lái)做調(diào)用鏈路追蹤。Skywalking不在此次的討論范圍中。
其次介紹一下項(xiàng)目的相關(guān)背景
調(diào)用路徑
項(xiàng)目中主要有兩種調(diào)用路徑
- Web請(qǐng)求走統(tǒng)一的網(wǎng)關(guān)入口,調(diào)用后端服務(wù)
- XXL-JOB定時(shí)任務(wù)執(zhí)行調(diào)度
部署環(huán)境
Kubernetes
問(wèn)題
走統(tǒng)一網(wǎng)關(guān)入口的請(qǐng)求不用擔(dān)心,在網(wǎng)關(guān)那邊加了TraceID,但是XXL-JOB由于是自動(dòng)注冊(cè),且部署環(huán)境是在K8S內(nèi),XXL-JOB獲取到的是Pod的IP,網(wǎng)關(guān)并未攔截到。
由于項(xiàng)目的邏輯較為復(fù)雜,XXL-JOB的調(diào)度任務(wù)屬于其中比較重要的一塊,對(duì)于前期開(kāi)發(fā)的調(diào)試以及后期問(wèn)題的確認(rèn),加上TraceID是非常有必要的。
二、方案
首先確認(rèn)是的XXLJOB執(zhí)行定時(shí)任務(wù)時(shí),JobHandler沒(méi)有TraceID,不考慮使用中間件的話,就只有兩種方案了。
一種是改造XXL-JOB源碼,在發(fā)起請(qǐng)求中添加TraceID;另一種則是在后端服務(wù)攔截到XXL-JOB的請(qǐng)求,在入口添加TraceID。
XXL-JOB的源碼沒(méi)有具體研究過(guò),之前只是做過(guò)適配Oracle,改造起來(lái)有一定難度,所以最后采用的方案還是在后端服務(wù)攔截請(qǐng)求,添加TraceID。
在網(wǎng)上搜索了一下相關(guān)資料,發(fā)現(xiàn)實(shí)現(xiàn)起來(lái)還是比較簡(jiǎn)單的,一般都是通過(guò)spring aop的方式,在Slf4j的MDC中添加TraceID。
在這里簡(jiǎn)單介紹下MDC,之前我也沒(méi)做過(guò)更多了解。
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j 和 logback 提供的一種方便在多線程條件下記錄日志的功能。某些應(yīng)用程序采用多線程的方式來(lái)處理多個(gè)用戶的請(qǐng)求。
MDC 可以看成是一個(gè)與當(dāng)前線程綁定的哈希表,可以往其中添加鍵值對(duì)。MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問(wèn)當(dāng)需要記錄日志時(shí),只需要從 MDC 中獲取所需的信息即可。
其實(shí)就是使用ThreadLocal來(lái)存儲(chǔ),而由于請(qǐng)求到Java后端服務(wù)時(shí),Tomcat會(huì)分配一個(gè)線程,直至請(qǐng)求結(jié)束,這樣就會(huì)保證我們?cè)谌肟谔砑拥腡raceID,會(huì)傳遞到整條鏈路。
但是使用MDC調(diào)用存在兩個(gè)問(wèn)題:
-
子線程中日志TraceID丟失
-
跨服務(wù)調(diào)用日志TraceID丟失
同時(shí)項(xiàng)目中使用了Openfeign,在發(fā)起端使用 RequestInterceptor 來(lái)攔截,添加TraceID,然后在接收端使用 HandlerInterceptor 攔截。
即最終方案是 MDC+RequestInterceptor+HandlerInterceptor
整體的調(diào)用鏈路如下:
暫時(shí)無(wú)法在飛書(shū)文檔外展示此內(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ù)改進(jìn)思路
上述方案有較大的局限性,只適用于服務(wù)間通過(guò)feign調(diào)用的方式,如果有其他如okhttp的方式,需要再添加攔截器。對(duì)于多線程的問(wèn)題也并未解決,常見(jiàn)的方式是通過(guò)重寫(xiě)線程池來(lái)解決。
-
豐富調(diào)用場(chǎng)景,添加攔截器
-
重寫(xiě)線程池
-
由于部署在K8S集群,可啟用Istio進(jìn)行服務(wù)治理