近期用到阿里的一款开源的数据同步工具 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是什么鬼?用法、源码一锅端的更多相关文章

  1. ThreadLocal 是什么鬼?用法、源码一锅端

    ThreadLocal 是一个老生常谈的问题,在源码学习以及实际项目研发中,往往都能见到它的踪影,用途比较广泛,所以有必要深入一番. 敢问,ThreadLocal 都用到了哪里?有没有运用它去解决过业 ...

  2. Android多线程全面解析:IntentService用法&源码

    前言 多线程的应用在Android开发中是非常常见的,常用方法主要有: 继承Thread类 实现Runnable接口 AsyncTask Handler HandlerThread IntentSer ...

  3. SQL-with as基本用法(源码DEMO)

    DROP TABLE #temp; with cr as ( SELECT At.SysNo AS AtSysNo , ( CASE WHEN At.Source = 1 THEN At.Vendor ...

  4. SQL-ROW_NUMBER() OVER函数的基本用法(源码案例)

    SELECT SUM(t.AdjustedBalance) AS Allqmye FROM ( SELECT * FROM ( SELECT ROW_NUMBER() OVER ( PARTITION ...

  5. OpenJDK源码研究笔记(十):枚举的高级用法,枚举实现接口,竟是别有洞天

    在研究OpenJDK,Java编译器javac源码的过程中,发现以下代码. 顿时发现枚举类竟然也有如此"高端大气上档次"的用法. 沙场点兵(用法源码) com.sun.tools. ...

  6. 【Vue2.x笔记3】从源码看watch对象

    初始化 function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = w ...

  7. c# winform 中的 工具栏自动隐藏 splitter用法 带源码

    c# winform 中的 工具栏自动隐藏 splitter用法 带源码 代码下载地址 http://download.csdn.net/detail/simadi/7649313

  8. springMVC源码分析--@SessionAttribute用法及原理解析SessionAttributesHandler和SessionAttributeStore

    @SessionAttribute作用于处理器类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的 ...

  9. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

随机推荐

  1. cmdb autoserver端表数据分析

    目录: 1.后台目录规划 autoserver: api: 接收数据并进行二次分析入库 API验证 backend : 负责后台管理 repository:负责管理模型类 (数据表) autoserv ...

  2. hdu3695 AC自动机优化

    题目链接:http://icpc.njust.edu.cn/Problem/Hdu/3695/ 不加last指针的AC自动机会T,原因是他费了很多功夫在跳转上,而last指针是直接直到跳转的终止位置, ...

  3. Mysql性能优化:为什么要用覆盖索引?

    导读 相信读者看过很多MYSQL索引优化的文章,其中有很多优化的方法,比如最佳左前缀,覆盖索引等方法,但是你真正理解为什么要使用最佳左前缀,为什么使用覆盖索引会提升查询的效率吗? 本篇文章将从MYSQ ...

  4. laravel如何实现多用户体系登录

    laraveli添加一个或多个用户表,以admin为例. 部分文件内容可能需要根据实际情况修改 创建一个Admin模型 php artisan make:model Admin -m 编写admins ...

  5. POJ 3070 Fibonacci矩阵快速幂 --斐波那契

    题意: 求出斐波那契数列的第n项的后四位数字 思路:f[n]=f[n-1]+f[n-2]递推可得二阶行列式,求第n项则是这个矩阵的n次幂,所以有矩阵快速幂模板,二阶行列式相乘, sum[ i ] [ ...

  6. web样式css

    css样式 什么是css 层叠样式表(Cascading Style Sheets),是一种用来表现HTML(标准通用标记语言的一个应用)或XML(标准通用标记语言的一个子集)等文件样式的计算机语言. ...

  7. 常见Web安全漏洞--------XSS 攻击

    1,XSS 攻击 XSS攻击使用Javascript脚本注入进行攻击 例如在提交表单后,展示到另一个页面,可能会受到XSS脚本注入,读取本地cookie远程发送给黑客服务器端. <script& ...

  8. 死磕Lambda表达式(六):Consumer、Predicate、Function复合

    你的无畏来源于无知.--<三体> 在上一篇文章(传送门)中介绍了Comparator复合,这次我们来介绍一下其他的复合Lambda表达式. Consumer复合 Consumer接口中,有 ...

  9. 使用IDEA创建SpringBoot项目

    SpringBoot学习第一步:搭建基础 IDEA对SpringBoot的项目支持可以说是点击就能完成基础的搭建,方便的不得了, 流程如下 1.左上角File选项,New project,选择Spri ...

  10. es6的箭头函数和es5的function函数区别

    一.es6的箭头函数es6箭头函数内部没有this,使用时会上朔寻找最近的this不可以做构造函数,不能使用new命令,因为没有this函数体内没有arguments,可以使用rest参数代替不能用y ...