MDC是什么⿁?⽤法、源码⼀锅端
近期⽤到阿⾥的⼀款开源的数据同步⼯具 Canal,不经意之中看到了 MDC 的⽤法,⽽且平时项⽬中也多次⽤到 MDC,趁机科普⼀把。通过今天的分享,能让你轻松 get 如下⼏点,绝对收获满满。
a)MDC 快速⼊门;
b)MDC 源码解读;
c)MDC 能⼲什么?
阿⾥开源项⽬ Canal:
⽼项⽬这么⽤过:
但是⽆论怎么⽤,都逃不过 MDC API 的使⽤,下⾯先花⼀分钟快速⼊门,然后再逐步去深⼊ MDC。
1. MDC 快速⼊门
MDC 全称是 Mapped Diagnostic Context,可以粗略的理解成是⼀个线程安全的存放诊断⽇志的容器。
⾸先看看 MDC 基本的 API 的⽤法,能抛代码的就不多废话(根据 logback 官⽅案例改编)。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.UUID;
/**
* MDC快速⼊门⽰例
*
* @author⼀猿⼩讲
*/
public class SimpleMDC {
private static final Logger logger = Logger(SimpleMDC.class);
public static final String REQ_ID = "REQ_ID";
public static void main(String[] args) {
MDC.put(REQ_ID, UUID.randomUUID().toString());
logger.info("开始调⽤服务A,进⾏业务处理");
logger.info("业务处理完毕,可以释放空间了,避免内存泄露");
logger.info("REQ_ID 还有吗?{}", (REQ_ID) != null);
}
}
代码编写完,貌似只有 MDC.put(K,V) 、ve(K) 两句是陌⽣的,先不着急解释它,等案例跑完就懂了,咱们继续往下看。
接下来配置 l,通过 %X{REQ_ID} 来打印 REQ_ID 的信息,l ⽂件内容如下。
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="CONSOLE" class="ch.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>[%t] [%X{REQ_ID}] - %m%n</Pattern>
</layout>
</appender>
<root level="debug">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
引⼊依赖包,让程序快点跑起来看看效果。
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>1.2.3</version>
log4j2 阿里</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
程序跑起来,输出截图如下。
根据输出结果分析,能够得到两条结论。
第⼀:如图中红⾊圈住部分所⽰,当 logback 内置的⽇志字段不能满⾜业务需求时,便可以借助 MDC 机制,将业务上想要输出的信息,通过 logback 给打印出来;
第⼆:如蓝⾊圈住部分所⽰,当调⽤ ve(Key) 后,便可将业务字段从 MDC 中删除,⽇志中就不再打印请求 ID 啦;
趁热打铁,我们迅速看看在多线程情况下,使⽤ MDC 会发⽣什么现象呢?
还是基于上⾯的代码,把代码段放到了线程体内,稍微进⾏改造了⼀下,代码如下。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.UUID;
/**
* MDC快速⼊门⽰例
*
* @author⼀猿⼩讲
*/
public class SimpleMDC {
public static void main(String[] args) {
new BizHandle("F0000").start();
new BizHandle("F9999").start();
}
}
class BizHandle extends Thread {
private static final Logger logger = Logger(SimpleMDC.class);
public static final String REQ_ID = "REQ_ID";
private String funCode;
public BizHandle(String funCode) {
this.funCode = funCode;
}
@Override
public void run() {
MDC.put(REQ_ID, UUID.randomUUID().toString());
logger.info("开始调⽤服务{},进⾏业务处理", funCode);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
logger.Message());
}
logger.info("服务{}处理完毕,可以释放空间了,避免内存泄露", funCode);
}
}
程序跑起来看看效果。
依据程序输出进⾏分析,能够看到线程 Thread-0 与 Thread-1 在 MDC 中放⼊的 REQ_ID 的值是互不影响,也就是说 MDC 中的值是与线程绑定在⼀起的。
好了,⼊门程序就这么简单,简单做个⼩结。
a)MDC 提供的 put ⽅法,可以将⼀个 K-V 的键值对放到容器中,并且能保证同⼀个线程内,Key 是唯⼀的,不同的线程 MDC 的值互不影响;
b)  在 l 中,在 layout 中可以通过声明 %X{REQ_ID} 来输出 MDC 中 REQ_ID 的信息;
c)MDC 提供的 remove ⽅法,可以清除 MDC 中指定 key 对应的键值对信息。
通过快速⼊门的程序,得知 MDC 的值与线程是绑定在⼀起的,不同线程互不影响,MDC 背后到底是怎么实现的呢?不妨从源码上看⼀看。
2. MDC 源码解读
解读源码之前,要提提 SLF4J,全称是 Simple Logging Facade for Java,翻译过来就是「⼀套简单的⽇志门⾯」。是为了让研发⼈员在项⽬中切换⽇志组件的⽅便,特意抽象出的⼀层。
项⽬开发中经常这么定义⽇志对象:
Logger logger = Logger(SimpleMDC.class)
其中 Logger 就来⾃于 SLF4J 的规范包,项⽬中⼀旦这样定义 Logger,在底层就可以⽆缝切换 logback、log4j 等⽇志组件啦,这或许就是Java 为什么要提倡要⾯向接⼝编程的好处。
见证奇迹的时刻要到了,下⾯就好好揭秘⼀下 MDC 背后藏着什么东东?
⾸先通过 org.slf4j.MDC 的源码,可以很清楚的知道 MDC 主要是通过 MDCAdapter 来完成 put、get、remove 等操作。
不出所料 MDCAdapter 也是个接⼝。在 Java 的世界⾥,应该都知道定义接⼝的⽬的:就是为了定义规范,让⼦类去实现。MDCAdapter 就和 JDBC 的规范类似,专门⽤于定义操作规范。JDBC 是为了定义数据库操作规范,让数据库⼚商(MySQL、DB2、Oracle 等)去实现;⽽ MDCAdapter 则是让具体的⽇志组件(logback、log4j等)去实现。
MDCAdapter 接⼝的实现类,有 NOPMDCAdapter、BasicMDCAdapter、LogbackMDCAdapter 以及 Log4jMDCAdapter 等等⼏种,其中log4j 使⽤的是 Log4jMDCAdapter,⽽ Logback 使⽤的是 LogbackMDCAdapter。
本次重点说 LogbackMDCAdapter 的源码,截图如下。
通过图中标注 1、2 的代码,可以清晰的知道 MDC 底层最终使⽤的是 ThreadLocal 来进⾏的实现(⽔落⽯出,花落它家)。
a)ThreadLocal 很多地⽅叫做线程本地变量,也有些地⽅叫做线程本地存储。
b)ThreadLocal 的作⽤是提供线程内的局部变量,这种变量在线程的⽣命周期内起作⽤,减少同⼀个线程内多个函数或者组件之间⼀些公共变量的传递的复杂度。
c)ThreadLocal 使⽤场景为⽤来解决数据库连接、Session 管理等。
本次不对 ThreadLocal 展开去说,若感兴趣的可⾃⾏填补⼀下。
3. MDC 能⼲什么?
MDC 的应⽤场景其实蛮多的,下⾯简单列举⼏个。
a)在 WEB 应⽤中,如果想在⽇志中输出请求⽤户 IP 地址、请求 URL、统计耗时等等,MDC 基本都能⽀撑;
b)在 WEB 应⽤中,如果能画出⽤户的请求到响应整个过程,势必会快速定位⽣产问题,那么借助 MD
C 来保存⽤户请求时产⽣的 reqId,当请求完成后,再将这个 reqId 进⾏移除,这么⼀来通过 grep reqId 就能轻松 get 整个请求流程的⽇志轨迹;
c)在微服务盛⾏的当下,链路跟踪是个难题,⽽借助 MDC 去埋点,巧妙实现链路跟踪应该不是问题。
4.写在最后
⾏⽂⾄此,接近尾声,本次主要让⼤家对 MDC 进⾏快速⼊门,并通过剖析源码,窥探 MDC 的背后,最终分享了⼀些 MDC 在项⽬研发中能做什么的实践思路,欢迎⼤家多去尝试实现。
另外,若是急需分布式调⽤链路跟踪、监控的轮⼦,在⾃研的轮⼦已经跟不上项⽬的发展时,有以下⼏款开源的轮⼦推荐,不妨拿去⼀试。
⼀起聊技术、谈业务、喷架构,少⾛弯路,不踩⼤坑,欢迎继续关注「⼀猿⼩讲」,会持续输出更多原创精彩分享!
可以搜索「⼀猿⼩讲」回复「1024」get 精⼼为你准备的编程进阶资料。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。