MDC实现微服务链路追踪
一、问题背景
在微服务架构中,我们没办法快速定位用户在一次请求中对应的所有日志,在排查生产问题的时候会非常困难,那是因为我们在输出的日志的时候没把请求的唯一标示输出到我们的日志中,导致我们没办法根据一个请求或者用户身份标识来做日志的过滤。
二、MDC简介
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
API说明:
clear() => 移除所有MDC
get (String key) => 获取当前线程MDC中指定key的值
getContext() => 获取当前线程MDC的MDC
put(String key, Object o) => 往当前线程的MDC中存入指定的键值对
remove(String key) => 删除当前线程MDC中指定的键值对 。
三、实现方式
由于 MDC 内部使用的是 ThreadLocal 所以只有本线程才有效,子线程和下游的服务 MDC 里的值会丢失,所以方案主要的难点是解决值的传递问题;
1. 工具类
public class TraceIdUtil {
public static final String TRACE_ID = "traceId";
public static String getTraceId() {
String traceId = MDC.get(TRACE_ID);
return traceId == null ? "" : traceId;
}
public static void setTraceId(String traceId) {
MDC.put(TRACE_ID, traceId);
}
public static void remove() {
MDC.remove(TRACE_ID);
}
public static void clear() {
MDC.clear();
}
public static String generateTraceId() {
return UUID.randomUUID().toString().replace("-", "");
}
}
- logback日志,这里的
[%X{traceId}]就是MDC中的,切不可写错key
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) [%X{traceId}] - %msg%n"/>
2. 拦截器
- 通过拦截器拦截请求,判断请求头中是否存在traceId,如果存在则存入MDC上下文中,不存在则生成traceId存入MDC中.
public class MdcInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上层调用就用上层的ID
String traceId = request.getHeader(TraceIdUtil.TRACE_ID);
if (StrUtil.isEmpty(traceId)) {
TraceIdUtil.setTraceId(TraceIdUtil.generateTraceId());
} else {
TraceIdUtil.setTraceId(traceId);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//调用结束后删除
TraceIdUtil.remove();
}
}
- 注册拦截器
@Component
public class WebAppConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 可添加多个
registry.addInterceptor(new MdcInterceptor()).addPathPatterns("/**");
}
}
3. 请求头传递
- 这里使用的是openFeign的解决方案,其他的类似,在请求头中塞入traceId
@Component
public class MyFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String traceId = TraceIdUtil.getTraceId();
// 传递请求头
if (StrUtil.isNotBlank(traceId)) {
requestTemplate.header(TraceIdUtil.TRACE_ID, traceId);
} else {
requestTemplate.header(TraceIdUtil.TRACE_ID, TraceIdUtil.generateTraceId());
}
}
}
4. 线程父子间传递
- 由于MDC的底层是ThreadLocal,所以会导致子线程拿不到主线程里的数据
public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(TraceIdUtil.TRACE_ID) == null) {
MDC.put(TraceIdUtil.TRACE_ID, TraceIdUtil.generateTraceId());
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MDC.clear();
}
};
}
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
//设置traceId
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}
- 自定义线程池
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 3940722618853093830L;
@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}
@Configuration
public class ThreadPoolTaskExecutorConfig{
//最大可用的CPU核数
public static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
@Bean
public ThreadPoolExecutorMdcWrapper getExecutor(){
ThreadPoolExecutorMdcWrapper executor =new ThreadPoolExecutorMdcWrapper();
executor.setCorePoolSize(PROCESSORS *2);
executor.setMaxPoolSize(PROCESSORS * 4);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("Task-A");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
- 单线程的做法(不建议)
public class MDCRunable implements Runnable {
private Map<String, String> copyOfContextMap;
private Runnable runnable;
public MDCRunable(Runnable runnable) {
this.copyOfContextMap = MDC.getCopyOfContextMap();
this.runnable = runnable;
}
@Override
public void run() {
if (!copyOfContextMap.isEmpty()) {
MDC.setContextMap(copyOfContextMap);
}
try {
runnable.run();
} finally {
if (!copyOfContextMap.isEmpty()) {
MDC.clear();
}
}
}
}
5. 测试结果
- 上游日志
2023-02-27 18:58:05 [http-nio-8099-exec-2] INFO c.s.c.controller.ConsumerController [65f8173c73f945d99ea5b0ab209164fd] - consumer-打印日志2
2023-02-27 18:58:05 [DefaultAsync-1] INFO c.s.c.controller.ConsumerController [65f8173c73f945d99ea5b0ab209164fd] - consumer-thread-01,测试线程
2023-02-27 18:58:05 [pool-9-thread-1] INFO c.s.c.controller.ConsumerController [65f8173c73f945d99ea5b0ab209164fd] - consumer-mdc-thread
- 下游日志
2023-02-27 18:58:05 [http-nio-8089-exec-1] INFO c.s.f.p.c.ProviderController [65f8173c73f945d99ea5b0ab209164fd] - provider-测试日志
2023-02-27 18:58:05 [DefaultAsync-1] INFO c.s.f.p.c.ProviderController [65f8173c73f945d99ea5b0ab209164fd] - provider-thread-02,测试线程
MDC实现微服务链路追踪的更多相关文章
- 阿里P7架构师详解微服务链路追踪原理
背景介绍 在微服务横行的时代,服务化思维逐渐成为了程序员的基本思维模式,但是,由于绝大部分项目只是一味地增加服务,并没有对其妥善管理,当接口出现问题时,很难从错综复杂的服务调用网络中找到问题根源,从而 ...
- 「Java分享客栈」随时用随时翻:微服务链路追踪之zipkin搭建
前言 微服务治理方案中,链路追踪是必修课,SpringCloud的组件其实使用很简单,生产环境中真正令人头疼的往往是软件维护,接口在微服务间的调用究竟哪个环节出现了问题,哪个环节耗时较长,这都是项目上 ...
- Gokit微服务-服务链路追踪
https://mp.weixin.qq.com/s/gjKOy4SDpsjUXDC3Q1YdFw Gokit微服务-服务链路追踪 原创: 兮一昂吧 兮一昂吧 2月28日
- 服务链路追踪(Spring Cloud Sleuth)
sleuth:英 [slu:θ] 美 [sluθ] n.足迹,警犬,侦探vi.做侦探 微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元.由于服务单元数量众多,业务的 ...
- spring cloud 入门系列八:使用spring cloud sleuth整合zipkin进行服务链路追踪
好久没有写博客了,主要是最近有些忙,今天忙里偷闲来一篇. =======我是华丽的分割线========== 微服务架构是一种分布式架构,微服务系统按照业务划分服务单元,一个微服务往往会有很多个服务单 ...
- Spring Cloud Sleuth+ZipKin+ELK服务链路追踪(七)
序言 sleuth是spring cloud的分布式跟踪工具,主要记录链路调用数据,本身只支持内存存储,在业务量大的场景下,为拉提升系统性能也可通过http传输数据,也可换做rabbit或者kafka ...
- Zipkin和微服务链路跟踪
https://cloud.tencent.com/developer/article/1082821 Zipkin和微服务链路跟踪 本期分享的内容是有关zipkin和分布式跟踪的内容. 首先,我们还 ...
- spring cloud微服务快速教程之(十一) Sleuth(zipkin) 服务链路追踪
0.前言 微服务架构上众多微服务通过REST调用,可能需要很多个服务协同才能完成一个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败.随着业务的不断扩张,服务之间互相调用 ...
- Spring Cloud Sleuth服务链路追踪(zipkin)(转)
这篇文章主要讲述服务追踪组件zipkin,Spring Cloud Sleuth集成了zipkin组件. 一.简介 Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案, ...
- SpringCloud(7)服务链路追踪Spring Cloud Sleuth
1.简介 Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可.本文主要讲述服务追踪组件zipki ...
随机推荐
- Elasticsearch实战:常见错误及详细解决方案
Elasticsearch实战:常见错误及详细解决方案 1.read_only_allow_delete":"true" 当我们在向某个索引添加一条数据的时候,可能(极少 ...
- 深度学习应用篇-计算机视觉-语义分割综述[6]:DeepLab系列简介、DeepLabV3深入解读创新点、训练策略、主要贡献
深度学习应用篇-计算机视觉-语义分割综述[6]:DeepLab系列简介.DeepLabV3深入解读创新点.训练策略.主要贡献 0.DeepLabV3深入解读 1.DeepLab系列简介 1.1.Dee ...
- LyScript 验证PE程序开启的保护
有些漏洞利用代码需要在某个保护模式被关闭的情况下才可以利用成功,在此之前需要得到程序开启了何种保护方式.验证其实有很多方法,其原理是读入PE文件头部结构,找到OPTIONAL_HEADER.DllCh ...
- 从嘉手札<2023-10-16>
一.商君书 1)更法 商鞅和甘龙.杜挚同秦孝公商量变法. 后两者认为变法会动移已有的社会结构,"圣人不易民而教,知者不变法而治""法古无过,循礼无邪" 但商鞅( ...
- centOS系统 迁移docker镜像及数据文件到指定目录
话说我今天正在快乐的敲代码,突然看到IDE报警磁盘空间不足了,du -h 查看了一下磁盘占用情况,发现是自己的docker镜像全部放/var/lib/docker目录下 这个系统磁盘给根目录只分配了5 ...
- 解决.netWebAPI输出时间格式带T问题
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add( new Ne ...
- JVM metaspace思维导图整理
JDK8中用元空间metaspace代替了永久代perm,原因和其特性简单介绍一下. 思维导图 图中gc log详解链接:https://www.jianshu.com/p/cd34d6f3b5b4 ...
- Qt processEvents - 解决线程中事件阻塞(如槽函数被阻塞)
百度了一会,发现没太有文字讲这件事情,因此整理成文字记录一下. processEvents介绍 长时间运行的操作可以调用processEvents() 保持应用程序响应能力. void QCoreAp ...
- .NET Core开发实战(第26课:工程结构概览:定义应用分层及依赖关系)--学习笔记
26 | 工程结构概览:定义应用分层及依赖关系 从这一节开始进入微服务实战部分 这一节主要讲解工程的结构和应用的分层 在应用的分层这里定义了四个层次: 1.领域模型层 2.基础设施层 3.应用层 4. ...
- Excel分类后数字类型的内容值后面变为0
背景 在工作中经常遇到从日志或者其他地方拷贝过来的文本,里面使用其他分隔符进行分割.然而,使用Excel的分列功能进行分列后,发现数字类型的数值后面变为0. 有时候我们就是需要原先的数值,该怎么办呢? ...