springboot~openfeign開啟熔(rong)斷之后(hou)MDC為null的(de)理解
MDC概念
MDC(Mapped Diagnostic Context,映射調試(shi)上下文)是 log4j 和 logback 提(ti)供(gong)的(de)一種方便(bian)在多線(xian)程條件下記錄(lu)日志(zhi)的(de)功能,也可(ke)以說是一種輕量級的(de)日志(zhi)跟蹤工具。
MDC能做什么
那么通過MDC的概念,我們可以知道,MDC是應用內的線程級別,不是分布式的應用層級別,所以僅靠它無法做到分布式應用調用鏈路跟蹤的需求。它要解決的問題主要是讓我們可以在海量日志數據中快速撈到可用的日志信息。
場景分析。
原理
既然(ran)(ran)我(wo)們(men)(men)知道MDC底(di)層使(shi)用TreadLocal來(lai)(lai)實現,那根(gen)據TreadLocal的(de)(de)特點,它是(shi)可以(yi)讓我(wo)們(men)(men)在同一個(ge)(ge)線(xian)(xian)程(cheng)中共享數據的(de)(de),但(dan)是(shi)往往我(wo)們(men)(men)在業(ye)務方法(fa)(fa)中,會開(kai)啟多線(xian)(xian)程(cheng)來(lai)(lai)執行程(cheng)序(xu),這(zhe)樣的(de)(de)話(hua)MDC就無法(fa)(fa)傳遞(di)(di)到其他(ta)子(zi)線(xian)(xian)程(cheng)了。這(zhe)時,我(wo)們(men)(men)需要使(shi)用額外的(de)(de)方法(fa)(fa)來(lai)(lai)傳遞(di)(di)存(cun)在TreadLocal里的(de)(de)值。MDC提供了一個(ge)(ge)叫getCopyOfContextMap的(de)(de)方法(fa)(fa),很顯然(ran)(ran),該(gai)方法(fa)(fa)就是(shi)把(ba)當前線(xian)(xian)程(cheng)TreadLocal綁定的(de)(de)Map獲取出來(lai)(lai),之后(hou)就是(shi)把(ba)該(gai)Map綁定到子(zi)線(xian)(xian)程(cheng)中的(de)(de)ThreadLocal中了,具體代(dai)碼如下:

圖中(zhong)有幾個(ge)MDCAdapter適(shi)配器,我們拿BasicMDCAdapter為例,它的(de)put,get這些方法,實(shi)事(shi)上是對InheritableThreadLocal<Map<String, String>>類型的(de)變量(liang)進(jin)行操作的(de),在主線(xian)程中(zhong)獲(huo)取(qu)值,將(jiang)值轉到子線(xian)程里使(shi)用(yong)它。
private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<Map<String, String>>() {
public Map<String, String> getCopyOfContextMap() {
Map<String, String> oldMap = inheritableThreadLocal.get();
if (oldMap != null) {
return new HashMap<String, String>(oldMap);
} else {
return null;
}
}
public void setContextMap(Map<String, String> contextMap) {
inheritableThreadLocal.set(new HashMap<String, String>(contextMap));
}
``
也就是說,我們在主線程中獲取MDC的值,然后在子線程中設置進去,這樣,子線程打印的信息也會帶有整個調用鏈共同的traceId了。
# openfeign
openfeign開啟熔斷之后MDC為null,這是有前提的,首先,你的熔斷開啟后,使用的是線程池的熔斷模式,即hystrix.command.default.execution.isolation.strategy=THREAD,或者不寫這行,如果值是`SEMAPHORE`模式,是可以獲取到MDC對象的,因為這種信號量模式,并沒有產生新的線程,所以對于ThreadLocal類型的MDC對象,是可以獲取到的。
# openFeign的熔斷配置
ribbon:
ribbon請求連接的超時時間- 限制3秒內必須請求到服務,并不限制服務處理的返回時間
connectTimeout: 1000
請求處理的超時時間 下級服務響應最大時間,超出時間消費方(路由也是消費方)返回timeout,超時時間不可大于斷路器的超時時間
readTimeout: 2000
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
strategy: THREAD #信號量模式,無超時時間;THREAD線程池模塊,這是默認的
thread:
timeoutInMilliseconds: 3000 #對THREAD模式才有效
feign:
hystrix:
# Feign啟用斷路器,默認為FALSE,如果開啟熔斷,如果是線程池模式,會在新線池中發起請求,這時MDC無論獲取到,如果是SEMAPHORE模式,是可以獲取到MDC的
enabled: true
# 在openFeign的攔截器中,獲取MDC中的traceId
> 注意,咱們的這個攔截器獲取traceId功能,只是在SEMAPHORE模式才有效【注意,這種模式的熔斷是沒有超時時間的,所以性能不太好,高并發時,請求慢的慢,容易堆積,造成服務器的雪崩】
@Configuration
public class FeignTraceIdInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String traceId = MDC.get(TRACE_ID);
if (traceId != null) {
template.header(HTTP_HEADER_TRACE, traceId);
}
}
}
在后面的調研中,我們還會針對THREAD模塊進行探究,找到獲取MDC中traceId的方法,請期待。