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 ...
随机推荐
- 「SWTR-04」Sweet Round 04 游记
比赛链接 由于 \(\texttt{Sweet Round}\) 比赛挺好的(关键不知道为啥\(Unrated\) 开篇总结(大雾):这次比赛题目不错(有思维含量) 尽管我不会做. 我一看 \(T1\ ...
- 图-搜索-DFS-37. 解数独
2020-03-24 22:23:32 问题描述: 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次.数字 1-9 在每一列只能出现一 ...
- Prism 源码解读6-事件聚合
0 介绍 事件提供的是1对多的绑定,通过委托链实现对订阅者的调用,事件必须要通过发布者调用.同时事件订阅是强引用,事件订阅者的生命周期总是大于等于事件发布者.如果代码中事件很多就会充斥着各种事件的订阅 ...
- 数据分析_numpy_基础2
数据分析_numpy_基础2 sqrt 开方 arr = np.arange(10) arr # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) np.sqrt(arr) ...
- Dome_iptest_server
一个简单的ip测试服务器 打印返回 请求头request import socket def send_data(conn, data=None, status=200): if status != ...
- windows server 2016 远程桌面mstsc DPI(更改文本、应用和其他项目大小) 设置
windows server 2016 远程桌面mstsc DPI 设置 在高分辨率机器2K,4K,8K,登入使用window远程桌面mstsc时,登入后虽然分辨率变成了和cilent一样分辨率 但是 ...
- .Net微服务实践(一):微服务框架选型
微服务框架 微服务(Microservices)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独立部署,各个微服务之间是松耦合的.每个微服务仅关注于完成一件任务并很 ...
- 使用RandomString方法后,结果返回相同的随机数解决办法
所遇问题: 在做超市管理系统的登录项目时,在对“随机数的产生”出现一个问题,在产生多个随机数的时候,出现了产生了多个一样的随机数,具体代码如下: /// <summary> /// 生成随 ...
- 利用Python批量重命名文件夹下文件
#!/usr/bin/python # -*- coding: UTF-8 -*- # -*- coding:utf8 -*- import os from string import digits ...
- D - 淡黄的长裙 HDU - 4221(贪心)
D - 淡黄的长裙 HDU - 4221(贪心) James is almost mad! Currently, he was assigned a lot of works to do, so ma ...