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 = LoggerFactory.getLogger(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("业务处理完毕,可以释放空间了,避免内存泄露");
MDC.remove(REQ_ID);
logger.info("REQ_ID 还有吗?{}", MDC.get(REQ_ID) != null);
}
}
代码编写完,貌似只有 MDC.put(K,V) 、MDC.remove(K) 两句是陌生的,先不着急解释它,等案例跑完就懂了,咱们继续往下看。
接下来配置 logback.xml,通过 %X{REQ_ID} 来打印 REQ_ID 的信息,logback.xml 文件内容如下。
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.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>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
程序跑起来,输出截图如下。

根据输出结果分析,能够得到两条结论。
第一:如图中红色圈住部分所示,当 logback 内置的日志字段不能满足业务需求时,便可以借助 MDC 机制,将业务上想要输出的信息,通过 logback 给打印出来;
第二:如蓝色圈住部分所示,当调用 MDC.remove(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 = LoggerFactory.getLogger(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.info(e.getMessage());
}
logger.info("服务{}处理完毕,可以释放空间了,避免内存泄露", funCode);
MDC.remove(REQ_ID);
}
}
程序跑起来看看效果。

依据程序输出进行分析,能够看到线程 Thread-0 与 Thread-1 在 MDC 中放入的 REQ_ID 的值是互不影响,也就是说 MDC 中的值是与线程绑定在一起的。
好了,入门程序就这么简单,简单做个小结。
a)MDC 提供的 put 方法,可以将一个 K-V 的键值对放到容器中,并且能保证同一个线程内,Key 是唯一的,不同的线程 MDC 的值互不影响;
b) 在 logback.xml 中,在 layout 中可以通过声明 %X{REQ_ID} 来输出 MDC 中 REQ_ID 的信息;
c)MDC 提供的 remove 方法,可以清除 MDC 中指定 key 对应的键值对信息。
通过快速入门的程序,得知 MDC 的值与线程是绑定在一起的,不同线程互不影响,MDC 背后到底是怎么实现的呢?不妨从源码上看一看。
2. MDC 源码解读
解读源码之前,要提提 SLF4J,全称是 Simple Logging Facade for Java,翻译过来就是「一套简单的日志门面」。是为了让研发人员在项目中切换日志组件的方便,特意抽象出的一层。
项目开发中经常这么定义日志对象:
Logger logger = LoggerFactory.getLogger(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 应用中,如果能画出用户的请求到响应整个过程,势必会快速定位生产问题,那么借助 MDC 来保存用户请求时产生的 reqId,当请求完成后,再将这个 reqId 进行移除,这么一来通过 grep reqId 就能轻松 get 整个请求流程的日志轨迹;
c)在微服务盛行的当下,链路跟踪是个难题,而借助 MDC 去埋点,巧妙实现链路跟踪应该不是问题。
4. 写在最后
行文至此,接近尾声,本次主要让大家对 MDC 进行快速入门,并通过剖析源码,窥探 MDC 的背后,最终分享了一些 MDC 在项目研发中能做什么的实践思路,欢迎大家多去尝试实现。
另外,若是急需分布式调用链路跟踪、监控的轮子,在自研的轮子已经跟不上项目的发展时,有以下几款开源的轮子推荐,不妨拿去一试。

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑,欢迎继续关注「一猿小讲」,会持续输出更多原创精彩分享!
可以微信搜索公众号「 一猿小讲 」回复「1024」get 精心为你准备的编程进阶资料。
MDC是什么鬼?用法、源码一锅端的更多相关文章
- ThreadLocal 是什么鬼?用法、源码一锅端
ThreadLocal 是一个老生常谈的问题,在源码学习以及实际项目研发中,往往都能见到它的踪影,用途比较广泛,所以有必要深入一番. 敢问,ThreadLocal 都用到了哪里?有没有运用它去解决过业 ...
- Android多线程全面解析:IntentService用法&源码
前言 多线程的应用在Android开发中是非常常见的,常用方法主要有: 继承Thread类 实现Runnable接口 AsyncTask Handler HandlerThread IntentSer ...
- SQL-with as基本用法(源码DEMO)
DROP TABLE #temp; with cr as ( SELECT At.SysNo AS AtSysNo , ( CASE WHEN At.Source = 1 THEN At.Vendor ...
- SQL-ROW_NUMBER() OVER函数的基本用法(源码案例)
SELECT SUM(t.AdjustedBalance) AS Allqmye FROM ( SELECT * FROM ( SELECT ROW_NUMBER() OVER ( PARTITION ...
- OpenJDK源码研究笔记(十):枚举的高级用法,枚举实现接口,竟是别有洞天
在研究OpenJDK,Java编译器javac源码的过程中,发现以下代码. 顿时发现枚举类竟然也有如此"高端大气上档次"的用法. 沙场点兵(用法源码) com.sun.tools. ...
- 【Vue2.x笔记3】从源码看watch对象
初始化 function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = w ...
- c# winform 中的 工具栏自动隐藏 splitter用法 带源码
c# winform 中的 工具栏自动隐藏 splitter用法 带源码 代码下载地址 http://download.csdn.net/detail/simadi/7649313
- springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore
@SessionAttribute作用于处理器类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的 ...
- 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)
一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...
随机推荐
- [A*,启发式搜索] [SCOI2005] 骑士精神
链接:https://ac.nowcoder.com/acm/problem/20247来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言52428 ...
- NLPer入门指南 | 完美第一步
介绍 你对互联网上的大量文本数据着迷吗?你是否正在寻找处理这些文本数据的方法,但不确定从哪里开始?毕竟,机器识别的是数字,而不是我们语言中的字母.在机器学习中,这可能是一个棘手的问题. 那么,我们如何 ...
- 一夜搞懂 | JVM 类加载机制
前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑Java的类加载机制,这是Java的一个很重要的创 ...
- [讲解]prim算法<最小生成树>
最小生成树的方法一般比较常用的就是kruskal和prim算法 一个是按边从小到大加,一个是按点从小到大加,两个方法都是比较常用的,都不是很难... kruskal算法在本文里我就不讲了,本文的重点是 ...
- 延时对象promise的使用
promise是ES6(ECMA Script6)的新标准,只能在支持ES6的浏览器中使用 Promise是一个延时对象,创建延时对象时需要传入一个函数类型的参数 这个函数有两个参数:resolve和 ...
- DEV Chart控件鼠标选中某条曲线
this.Chart.ObjectSelected += new DevExpress.XtraCharts.HotTrackEventHandler(this.Chart_ObjectSelecte ...
- 接口测试彻底弄懂Session、Cookie、Token的区别及联系hold住面试官--hold住了开3万,hold不住开3K!
一.前言:接口测试之伤:cookie,session,token本是一家! cookie,session,token的区别早就已经成为测试同行的心病,各大论坛,各大博客,各大视频网站无不充斥着各种疑问 ...
- Mac电脑之间的文件共享 - 偏门
文件共享是工作中经常要进行的. Mac用户之间可以通过AirDrop来共享文件.AirDrop要借助无线网络,而很多人都是将Mac做成个人热点供手机等Wifi连接,AirDrop时必须断开热点,不方便 ...
- Centos8中安装JDK1.8
在这里是通过yum命令进行安装的 安装前检查是否安装了jdk # java -version 如果使用 yum 安装的 jdk,请使用下面命令卸载 yum -y remove java-1.8.0-o ...
- Html 慕课园编程练习9-22
题目要求: 制作一个表格,显示班级的学生信息. 要求: 1. 鼠标移到不同行上时背景色改为色值为 #f2f2f2,移开鼠标时则恢复为原背景色 #fff 2. 点击添加按钮,能动态在最后添加一行 3. ...