分布式 日志追踪 链路追踪

日志追踪码传递

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。

MDC内部使用的是ThreadLocal所以只有本线程才有效

slf4j MDC源码中 没有用 ThreadLocal 就是普通的Map

使用 ThreadLocal 的是其他的日志框架覆盖 MDC实现

比如 logback 就是在自身的包中覆盖了 org.slf4j 包中的部分类

logback 中的MDC 就是使用 ThreadLocal

在 微服务当前切换 服务传递追踪码

gateway 网关传递

直接在转发的请求头中携带即可

Feign Hystrix

网上资料

网上的写法都是介绍 HystrixRequestVariableDefault

feign 如果有用 hystrix 传递时有涉及到跨线程 需要使用 HystrixRequestVariableDefault 传递

或者重写 wrapCallable 方法

1
2
3
4
public <T> Callable<T> wrapCallable(Callable<T> callable) {
//TransmittableThreadLocal修饰原有Callable
return TtlCallable.get(callable);
}

还需要按最后 去替换线程池

和 在Configuration配置自定义 HystrixConcurrencyStrategy 替换默认的 HystrixConcurrencyStrategyDefault

1
2
3
4
5
6
7
8
@Configuration
public class HystrixConfig {
@Bean
public HystrixConcurrencyStrategy requestContextHystrixConcurrencyStrategy() {
return new MyHystrixConcurrencyStrategy();
}
}

此处参考了 https://shanhy.blog.csdn.net/article/details/108668952

实测都不太方便

  1. HystrixRequestVariableDefault 要用 HystrixContextRunnable 或 HystrixContextCallable创建线程才能在线程间传递数据
  2. wrapCallable 测试无效

HystrixInvocationHandler

阅读源码后发现 wrapCallable 装饰是在 HystrixInvocationHandler.invoke 之前就调用了

invoke 中 还有一个 HystrixCommand 进行回调

等于是 Hystrix 有两层 调用 第一层时参数还有传递 第二层回调时切换线程导致传递的参数丢失

覆盖 HystrixInvocationHandler 类 调用自定义的 HystrixCommand 进行传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Lqs1848HystrixCommand<R> extends HystrixCommand<R>{
private String trace;//你要传递的参数
protected Lqs1848HystrixCommand(Setter setter) {
super(setter);
this.trace = TraceUtils.getTrace();//获取到当前线程的值
}

@Override
protected R run() throws Exception {
TraceUtils.setTrace(trace);//线程池执行时 把值传递给线程池
try {
return runMain();
} finally {
TraceUtils.clear();
}
}
protected abstract R runMain() throws Exception;
}

dubbo 传递

带隐藏参数即可 在RpcContext 中携带

父子线程传递

网上的方法都是

https://yanglinwei.blog.csdn.net/article/details/113503577

https://segmentfault.com/a/1190000020083061

https://juejin.cn/post/6844904128351567885

https://blog.csdn.net/zlt2000/article/details/99641821

内容都一样也不知道是谁抄谁的

其中最重要的是第一行

package org.slf4j;

要覆盖掉 slf4j 中的MDCAdapter

MDC.mdcAdapter = mdcAdapter;

这个根本就不重要

只要和 logback 一样实现 把 org.slf4j.impl 实现了就行 重写 StaticMDCBinder 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StaticMDCBinder {

public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();

private StaticMDCBinder() {
}

public MDCAdapter getMDCA() {
//这里替换成功自己的自定义的MDC
return new MyMDCAdapter();
}

public String getMDCAdapterClassStr() {
return MysdMDCAdapter.class.getName();
}
}


线程池替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

/**
* 线程池配置
**/
@Configuration
public class ThreadPoolConfig
{
// 核心线程池大小
private int corePoolSize = 8;

// 最大可创建的线程数
private int maxPoolSize = 50;

// 队列最大长度
private int queueCapacity = 1000;

// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 180;

@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}//method

/*
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return TtlExecutors.getTtlExecutor(executor);
}//method
*/


/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService()
{
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
Threads.printException(r, t);
}
};

return TtlExecutors.getTtlScheduledExecutorService(scheduledExecutorService);
}//method
}//class

@Async

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;

@Override
public Executor getAsyncExecutor() {
//返回你替换过的 executor
//或者用默认线程池 然后设置 TaskDecorator
return threadPoolTaskExecutor;
}

}

MQ传递

我的mq调用都是用的 json格式数据

传输数据时携带上 追踪码即可

总结

跨线程

使用 阿里的 TransmittableThreadLocal

把有提供 回调修饰的方法用 TransmittableThreadLocal提供的工具修饰一下

ThreadLocal<> 替换为 TransmittableThreadLocal<>

再把 所有使用的线程池 换成自己的线程池即可

替换线程池 和 修饰 Callable/Runnable 二者实现其一即可

Hystrix的线程池我就没有替换 而是修饰了 Callable