MDC
A Mapped Diagnostic Context, or MDC in short, is an instrument for distinguishing interleaved log output from different sources. Log output is typically interleaved when a server handles multiple clients near-simultaneously.
The MDC is managed on a per thread basis. Note that a child thread does not inherit the mapped diagnostic context of its parent.
映射診斷上下文萝衩,簡(jiǎn)稱MDC尊沸,是一種用于區(qū)分來自不同來源的交錯(cuò)日志輸出的工具。當(dāng)服務(wù)器幾乎同時(shí)處理多個(gè)客戶端時(shí)仪壮,日志輸出通常是交錯(cuò)的喜滨。
MDC基于每個(gè)線程進(jìn)行管理。請(qǐng)注意,子線程不繼承其父線程的MDC。
以上解釋來自LogbackMDCAdapter
(pom定義如下)
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.7</version>
</dependency>
ThreadLocal的功能是為當(dāng)前線程維護(hù)一個(gè)map
org.slf4j.MDC
基于ThreadLocal忆肾,集成了日志組件,具體實(shí)現(xiàn)有Log4jMDCAdapter和LogbackMDCAdapter
org.slf4j.MDC
的ThreadLocal里的key菱肖,可以在log4j/logback.xml中定義占位符,并輸出到日志
比如旭从,執(zhí)行MDC.put("traceId", traceId)
稳强,logback.xml中定義如下,最后輸出的日志和悦,就能打印出traceId
<!--格式化輸出:%d表示日期退疫,%thread表示線程名,%traceId表示分布式鏈路id鸽素,%-5level:級(jí)別從左顯示5個(gè)字符寬度褒繁,%msg表示日志消息,%n表示換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%traceId] %-5level - %msg%n</pattern>
至于MDC中的ThreadLocal馍忽,有沒有跨線程復(fù)制的能力(父線程的上下文復(fù)制到子線程)棒坏,與MDCAdapter的版本有關(guān)。
在1.4.7版本的LogbackMDCAdapter
遭笋,注釋寫的是Note that a child thread does not inherit the mapped diagnostic context of its parent. 子線程不繼承父線程的MDC坝冕。
在1.2.9版本的LogbackMDCAdapter
,注釋寫的是A child thread automatically inherits a copy of the mapped diagnostic context of its parent. 子線程自動(dòng)繼承父線程的MDC瓦呼。
Java Instrument && Java Agent
Java Instrument
Java Instrument是JDK1.5提供的一個(gè)新特性喂窟,用于監(jiān)測(cè)JVM進(jìn)程,甚至可以修改類的實(shí)現(xiàn)央串。有了這樣的功能磨澡,就可以更靈活的實(shí)現(xiàn)運(yùn)行時(shí)虛擬機(jī)監(jiān)控和Java類操作,這樣的特性可以看做是一種虛擬機(jī)級(jí)別的AOP實(shí)現(xiàn)方式质和。
ClassFileTransformer是類文件的轉(zhuǎn)換器
Instrumentation提供監(jiān)測(cè)Java程序所需的服務(wù)
Java Agent
Java Agent是一種特殊的Java程序稳摄,可以看做是Java Instrumentation的客戶端。普通Java程序通過main函數(shù)啟動(dòng)侦另,Java Agent無法單獨(dú)啟動(dòng)秩命,必須依附于一個(gè)Java程序上,與他運(yùn)行在同一個(gè)進(jìn)程中褒傅,通過Java Instrumentation的客戶端 API與虛擬機(jī)交互弃锐。
JVM會(huì)把Instrumentation的實(shí)例作為參數(shù)注入到Java Agent的啟動(dòng)方法上,因此要使用Instrumentation功能殿托,必須通過Java Agent霹菊。
Java Agent有2個(gè)加載時(shí)機(jī),一個(gè)是JVM啟動(dòng)前通過-javaagent參數(shù)靜態(tài)加載執(zhí)行,另一個(gè)是JVM啟動(dòng)后通過Java Tool API中的attach api動(dòng)態(tài)加載執(zhí)行旋廷。
skywalking
skywalking的主要作用是以javaAgent的形式(也可以有別的形式)鸠按,收集應(yīng)用程序的鏈路信息(請(qǐng)求入?yún)ⅰ㈨憫?yīng)結(jié)果饶碘、耗時(shí)等等)目尖,上傳到存儲(chǔ)組件,并提供可視化展示扎运。
源碼倉庫: https://github.com/apache/skywalking.git
分支: 6.x
Trace注解
方法上添加了@Trace注解瑟曲,會(huì)執(zhí)行TraceAnnotationMethodInterceptor攔截器,攔截器中會(huì)創(chuàng)建span
package org.apache.skywalking.apm.toolkit.activation.trace;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.DeclaredInstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import org.apache.skywalking.apm.agent.core.plugin.match.MethodAnnotationMatch;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* {@link TraceAnnotationActivation} enhance all method that annotated with <code>org.apache.skywalking.apm.toolkit.trace.annotation.Trace</code>
* by <code>TraceAnnotationMethodInterceptor</code>.
*
* @author zhangxin
*/
public class TraceAnnotationActivation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String TRACE_ANNOTATION_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.toolkit.activation.trace.TraceAnnotationMethodInterceptor";
public static final String TRACE_ANNOTATION = "org.apache.skywalking.apm.toolkit.trace.Trace";
@Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[0];
}
@Override public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new DeclaredInstanceMethodsInterceptPoint() {
@Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
return isAnnotatedWith(named(TRACE_ANNOTATION));
}
@Override public String getMethodsInterceptor() {
return TRACE_ANNOTATION_METHOD_INTERCEPTOR;
}
@Override public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override protected ClassMatch enhanceClass() {
return MethodAnnotationMatch.byMethodAnnotationMatch(new String[] {TRACE_ANNOTATION});
}
}
TraceCrossThread注解
類上添加了@TraceCrossThread注解豪治,并且存在名為call, run, get的方法洞拨,會(huì)執(zhí)行CallableOrRunnableInvokeInterceptor攔截器,攔截器中會(huì)創(chuàng)建span
package org.apache.skywalking.apm.toolkit.activation.trace;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import static net.bytebuddy.matcher.ElementMatchers.*;
import static org.apache.skywalking.apm.agent.core.plugin.match.ClassAnnotationMatch.byClassAnnotationMatch;
/**
* {@link CallableOrRunnableActivation} presents that skywalking intercepts all Class with annotation
* "org.skywalking.apm.toolkit.trace.TraceCrossThread" and method named "call" or "run".
*
* @author carlvine500 lican
*/
public class CallableOrRunnableActivation extends ClassInstanceMethodsEnhancePluginDefine {
public static final String ANNOTATION_NAME = "org.apache.skywalking.apm.toolkit.trace.TraceCrossThread";
private static final String INIT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.toolkit.activation.trace.CallableOrRunnableConstructInterceptor";
private static final String CALL_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.toolkit.activation.trace.CallableOrRunnableInvokeInterceptor";
private static final String CALL_METHOD_NAME = "call";
private static final String RUN_METHOD_NAME = "run";
private static final String GET_METHOD_NAME = "get";
@Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[] {
new ConstructorInterceptPoint() {
@Override public ElementMatcher<MethodDescription> getConstructorMatcher() {
return any();
}
@Override public String getConstructorInterceptor() {
return INIT_METHOD_INTERCEPTOR;
}
}
};
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
return (named(CALL_METHOD_NAME).and(takesArguments(0)))
.or(named(RUN_METHOD_NAME).and(takesArguments(0)))
.or(named(GET_METHOD_NAME).and(takesArguments(0)));
}
@Override public String getMethodsInterceptor() {
return CALL_METHOD_INTERCEPTOR;
}
@Override public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override protected ClassMatch enhanceClass() {
return byClassAnnotationMatch(new String[] {ANNOTATION_NAME});
}
}
ThreadLocal
java.lang.ThreadLocal無法復(fù)制上下文至子線程
java.lang.InheritableThreadLocal只有在線程創(chuàng)建時(shí)负拟,把父線程的上下文復(fù)制到子線程
阿里開源的com.alibaba.ttl.TransmittableThreadLocal烦衣,可以在子線程執(zhí)行任務(wù)前,把父線程的上下文復(fù)制到子線程
pom
<!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.12.0</version>
</dependency>
跨進(jìn)程傳遞上下文
TransmittableThreadLocal只能解決同進(jìn)程內(nèi)跨線程復(fù)制的問題掩浙,如果需要跨進(jìn)程復(fù)制上下文花吟,需要有攔截器。
以分布式鏈路id traceId的實(shí)現(xiàn)為例涣脚,
比如A服務(wù)http請(qǐng)求B服務(wù)示辈,可能會(huì)通過Feign, RestTemplate, HttpClient等形式。需要為項(xiàng)目中用到的每個(gè)形式遣蚀,定義攔截器矾麻,發(fā)送http請(qǐng)求時(shí),從MDC取出traceId芭梯,放到http header里险耀,key為trace-id。
B服務(wù)也需要有一個(gè)攔截器玖喘,從http header里拿出trace-id甩牺,放到MDC里。
如果是mq的場(chǎng)景累奈,也是一樣的
生產(chǎn)者需要從MDC取出traceId贬派,放到mq的properties里面
消費(fèi)者需要從mq的properties里,拿出traceId澎媒,放入MDC