日志全链路追踪之MDC

news2024/11/17 20:35:13

1.引言

Hi,大家好,我是有清

苏格拉底说过:日志打得好,排查没烦恼

我们日常的开发工作中,排查问题去看日志应该是家常便饭的事,日志可以帮助我们清楚的知道当前代码的走向以及链路数据,通常我们现在都是微服务的架构,那么在我们 A 系统调用到 B 系统中,B 系统又调用 C 系统 t9e3jG

在繁忙的日志中,我们如何全链路追踪一笔调用呢,通常我们会借助到全局流水号这样的概念,例如下图这样

AM0I2d

实现这样的追踪方式有很多种,我们今天入门比较经典实现方式的一种-MDC,MDC 是 SLF4J 提供的一种方便在多线程下记录日志的工具类,可以帮助我们快速实现我们上述说到的全局流水号的打印

2.基本使用

2.1 日志格式配置

日志格式配置.png

2.2 主线程下进行日志打印

我们可以看到输出中将我们的 trace_id 打印出了,就是这么简单

2.3 多线程下进行日志打印

我们刚才在引言中说过,MDC 支持在多线程下进行日志打印,接下来我们同样来一个小 demo 看看

主线程下进行日志打印.png

多线程下进行日志打印2.png 同样实现了,我们的诉求,那么,MDC 为何如此丝滑的实现了我们的想法呢?

3.源码解析

debug 之下无秘密

Java MDC.put(TRACE_ID, UUID.randomUUID().toString());

我们先从这个 put 方法点击进去,看看 MDC 到底是何方神圣

Java public static void put(String key, String val) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key parameter cannot be null"); } if (mdcAdapter == null) { throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL); } mdcAdapter.put(key, val); }

可以看出来,其实主要借助的是这个这个 mdcAdapter 这个类,这个 Adapter,是什么意思么。过了四级的小伙伴举手了,这个就是适配器的意思,对就是适配器

那么,有一道经典面试必问题不就有答案了么,你看过框架中的设计模式么?我看过 SLF4J 中的的适配器模式,它提供的 mdcAdapter,但是具体实现是交由系统中的日志组件,比如我项目中使用的 LogBack,那么 LogBack 就提供了 LogbackMDCAdapter

在 MDC 类的的静态方法中,直接实现了 mdcAdapter 的加载

Java static { try { mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA(); } catch (NoClassDefFoundError ncde) { mdcAdapter = new NOPMDCAdapter(); String msg = ncde.getMessage(); if (msg != null && msg.indexOf("StaticMDCBinder") != -1) { Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\"."); Util.report("Defaulting to no-operation MDCAdapter implementation."); Util .report("See " + NO_STATIC_MDC_BINDER_URL + " for further details."); } else { throw ncde; } } catch (Exception e) { // we should never get here Util.report("MDC binding unsuccessful.", e); } }

这边获取到的 mdcAdapter 即 LogbackMDCAdapter,为什么可以如此无感的获取到呢,其实这边还有一种机制就是 SPI 机制,感兴趣的小伙伴可以研究一下

那么知道了其实是 LogbackMDCAdapter 起作用,我们再进一步看下,LogbackMDCAdapter 的源码

```Java public void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}

Map<String, String> oldMap = copyOnThreadLocal.get();  
Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);  

if (wasLastOpReadOrNull(lastOp) || oldMap == null) {  
    Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);  
    newMap.put(key, val);  
} else {  
    oldMap.put(key, val);  
}

} ```

LogbackMDCAdapter 中的 put 方法看起来也很朴素,我们一行行来看,首先对 key 进行判空处理,如果是空的,那么你存个 der,异常抛出

Java Map<String, String> oldMap = copyOnThreadLocal.get();

这一段是为了获取上次操作的 map,copyOnThreadLocal 听起来就是为了复制上次的 ThreadLocal,其实也是我们的数据存放的地方

Java Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);

紧接着通过 WRITE_OPERATION 获取 lastOp,这个last 就是 lastOperation,翻译一下 最后一次的操作, WRITE_OPERATION 默认为 1

```Java if (wasLastOpReadOrNull(lastOp) || oldMap == null)

private boolean wasLastOpReadOrNull(Integer lastOp) {
return lastOp == null || lastOp.intValue() == MAPCOPYOPERATION;
} ```

这边的 wasLastOpReadOrNull,通过函数名字我们可以知道,是为了判断最后一次操作是不是读操作或者是第一次进来写操作

这边为什么要判断是不是读操作其实是为了确认最后一次快照读的数据的正确性,但是如果使用 MDC 接口直接进行操作,是不会涉及到快照读的数据的正确性,无需深究

```Java Map newMap = duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);

private Map duplicateAndInsertNewMap(Map oldMap) {
Map newMap = Collections.synchronizedMap(new HashMap ());
if (oldMap != null) {
// we don't want the parent thread modifying oldMap while we are
// iterating over it
synchronized (oldMap) {
newMap.putAll(oldMap);
}
}

copyOnThreadLocal.set(newMap);  
return newMap;

} ```

这边会构造一个线程安全的 newMap,并且锁住旧 map,这边我们知道 oldMap 其实已经是线程安全的 map 了,那么为何又加了一把锁呢?这边考虑的很全面,为什么加锁呢,是因为Collections.synchronizedMap 的父子线程是可以重入的,再加一把锁避免父线程对数据进行篡改

至于为何不使用 CurrentHashMap,我也没有猜透,莫非仅仅是为了面向接口编程,使用 Collections.synchronizedMap 保证可以接受任何的 Map 实例?

如果不是第一次进来或者上次操作是写的话,直接会对 oldMap 直接再进行赋值

简单做个总结,MDC 实现的原理其实很简单,就是利用 ThredLocal 保证不同线程下的 Key 不受影响

4.MDC 局限性

在理解了 MDC 的实现之后,比较有经验的小伙伴应该就会想出 MDC 的局限性,那就是父子线程无法传递数据的问题

show me code

我们在 main 线程设置了 traceId,但是开启线程池之后,却没有将我们的 traceId 打印出来,当然官方文档也解释了这一问题: https://logging.apache.org/log4j/2.x/manual/thread-context.html

其实官方就是推荐我们使用 InheritableThreadLocal 去实现父子线程的值传递

当然我们还可以在子线程中直接捞出 MDC 中的 Map,强行进行赋值,示例代码如下

父子线程正确.png 当然也有现有框架帮我们直接一步解决了这种弊端,感兴趣的小伙伴可以去体验一下 qDu1jI

闲言碎语

写完这篇文章的时候,电脑正在播放着梁博的日落大道

每次听这首歌,总有一种感觉,自己开着车,在一望无际的公路上行驶

在车上,会看到暖风将至,会看到温柔满意,会看到黄昏追逐黎明,会看到那耀眼的金黄

vQaudH

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/737431.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript 使用链接跳转传递数组对象数据类型的方法

文章目录 首先了解一下正常传递基本数据类型JavaScript 跳转页面方法JavaScript 路由传递参数JavaScript 路由接收参数传递对象效果&#xff1a; 在前端有的时候会需要用链接进行传递参数&#xff0c;基本数据类型的传递还是比较简单的&#xff0c;但是如果要传递引用数据类型就…

Python实现PSO粒子群优化算法优化随机森林分类模型(RandomForestClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 PSO是粒子群优化算法&#xff08;Particle Swarm Optimization&#xff09;的英文缩写&#xff0c;是一…

WH5097D有源矩阵驱动的Mini LED背光应用方案

Miniled技术为lcd的全面升级版&#xff0c;Miniled的背光层在单位面积内可以容纳更多LED&#xff0c;从而大大提高背光源数量&#xff0c;因此可以进行区域亮度调节的设计&#xff0c;从而在个别区域实现关闭led从而达到完全的黑色&#xff0c;不仅减小了功耗&#xff0c;而且由…

【KingbaseES】如何查看表结构

SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name test_szie;

梅雨季“霉”烦恼,防潮自救指南请收好

魔都的雨下个不停&#xff0c;天气也异常闷热&#xff0c;原来是上海已经入梅了。“雨连连、湿哒哒、闷兮兮”的梅雨季&#xff0c;湿漉漉的空气&#xff0c;感觉身体也跟着“发霉”。不想做梅雨季最“潮”人&#xff0c;赶紧码住这份抗“霉”攻略。 梅雨季最大的特点是空气湿度…

数据库DDL

目录 DDL数据库的操作 SQL表操作&#xff1a; 数据库的数据类型&#xff1a; 数值类型​编辑 字符串类型 日期时间类型 DDL表操作 - 修改​编辑 总结&#xff1a; DDL数据库的操作 演示&#xff1a; SQL表操作&#xff1a; 数据库的数据类型&#xff1a; 数值类型 字符串…

Person相关系数

衡量两个变量线性相关程度。先画散点图看是否为线性相关&#xff0c;相关系数才有用。 总体&#xff1a;要考察对象的全部个体 样本&#xff1a;从总体中所抽取的一部分个体 用样本的统计量估计总体的统计量 总体Person相关系数 协方差Cov(X,Y)反映X、Y的相关性 Person相关系…

zabbix 应用(二)

目录 一&#xff1a;部署 zabbix 代理服务器 1、准备环境 2、 设置 zabbix 的下载源&#xff0c;安装 zabbix-proxy 3、 部署数据库&#xff0c;要求 MySQL 5.7 或 Mariadb 10.5 及以上版本 4、 初始化数据库 5、创建数据库 指定字符集&#xff0c;创建 zabbix 数据库用户…

从零开始的知识图谱生活,构建一个百科知识图谱,完成基于Deepdive的知识抽取、基于ES的简单语义搜索、基于 REfO 的简单KBQA

项目设计集合&#xff08;人工智能方向&#xff09;&#xff1a;助力新人快速实战掌握技能、自主完成项目设计升级&#xff0c;提升自身的硬实力&#xff08;不仅限NLP、知识图谱、计算机视觉等领域&#xff09;&#xff1a;汇总有意义的项目设计集合&#xff0c;助力新人快速实…

华为云“All in ”大模型:释放人工智能巨能!看低代码开发平台引领未来

前言 截至目前&#xff0c;全球已发布数百个大模型&#xff0c;我国年内已发布80多个大模型&#xff0c;面向消费者端的应用百花齐放。我们认为&#xff0c;大模型在行业里多作贡献&#xff0c;才是正确的道路。 当下发展现状 今年以来&#xff0c;人工智能的发展因ChatGPT进入…

MAC |如何在mac上阅读caj文件?

背景&#xff1a;工作群中老板突然发了一个caj的论文过来&#xff0c;让大家阅读学习。 于是就开启了解决&#xff1a;在mac上阅读caj文件之旅。 首先&#xff0c;尝试了这篇文章的方法&#xff1a; 教你如何在Mac上打开CAJ格式的文件_普通网友的博客-CSDN博客 以失败告终。…

【sql注入-报错注入1】extractvalue()函数 报错注入

目录 extractvalue()报错注入 一、语法介绍&#xff1a; 二、报错原因 网络安全小圈子 &#xff08;***注&#xff1a;注意看版本要求&#xff09; extractvalue()报错注入 一、语法介绍&#xff1a; 版本&#xff1a; MySQL<5.0.x 语法&#xff1a; EXTRACTVALUE(…

地址解析协议 (ARP)

地址解析协议&#xff08;ARP&#xff09;是互联网协议&#xff08;IP&#xff09;套件的关键第 2 层协议&#xff0c;可将 IP 地址转换为媒体访问控制&#xff08;MAC&#xff09;地址&#xff08;IP – MAC&#xff09;&#xff0c;ARP 在实现网络连接方面发挥着不可或缺的作…

赛效:如何一键生成印章

1&#xff1a;在电脑上打开标小智印章生成器&#xff0c;点击输入框&#xff0c;在输入框里输入印章内容。 2&#xff1a;文本内容输入后&#xff0c;点击右侧的“生成按钮”。 3&#xff1a;在生成的印章模板里&#xff0c;挑一个满意的&#xff0c;鼠标放上去就可以看到下载按…

安装Nodejs、NPM、Vue脚手架详细教程

一、安装Nodejs 查看自己电脑是否安装nodejs node --version我这里已经下载过了&#xff0c;没有下载过的会提示该命令不存在 可以到官网下载一下 https://nodejs.org/en/download/ 不要安装在中文路径下 二、安装NPM 如果你安装了nodejs–默认会安装NPM. npm --version…

国内好用的CRM框架推荐和介绍

一、如何选择CRM管理系统的方法 选择适合自己的CRM管理系统是企业客户关系管理的重要决策之一&#xff0c;需要根据自身的需求和实际情况进行选择。下面介绍几个选择比较好的CRM管理系统的方法&#xff1a; 1. 确定功能需求&#xff1a;企业需要根据自身的业务特点和管理需求…

OpenHarmony之小熊派Bearpi-hm_micro_small刷机问题避坑

目录 1.概述2.注意事项3.发现问题4.解决问题 1.概述 最近大家都知道&#xff0c;华为出了一个中国的手机操作系统HarmonyOS,本人很是激动&#xff0c;因为中国终于有了自己的手机操作系统&#xff0c;而且我去了解了下&#xff0c;发现完全不同于Android和IOS操作系统&#xf…

集合面试题--LinkedList数组

目录 单向链表 介绍 时间复杂度分析 双向链表 时间复杂度分析 总结 ArrayList和LinkedList的区别是什么&#xff1f; 单向链表 介绍 时间复杂度分析 双向链表 时间复杂度分析 总结 ArrayList和LinkedList的区别是什么&#xff1f;

Drools用户手册翻译——第三章 构建,部署,应用和运行(一)介绍与构建

这一章内容颇多&#xff0c;就是一个构建&#xff0c;就翻译了好久&#xff0c;虽然说之前用过drools&#xff0c;但是里面kie相关的很多类都比较混乱&#xff0c;翻译完这个用户手册&#xff0c;感觉清晰了许多&#xff0c;因为实在是太多了&#xff0c;如果你也有相同的情况&…

pytorch grid_sample易错点

pytorch grid_sample易错点 易错点是&#xff1a; grid_sample函数中, x对应w, y对应h !! grid_sample函数中, x对应w, y对应h !! grid_sample函数中, x对应w, y对应h !! 函数的作用 output的size和grid的size是一样的&#xff0c;所以output中某一位置(h, w)的值&#xff0c…