TheadlLocal用法之基于SpringAOP的全局日志跟踪

写博客的目的在于自我省,自我总结,自我梳理

烦恼:

你是否有如下烦恼:你的系统日志打印非常乱,每次排查问题需要根据业务id去查询每个对应服务中这次请求所对应的日志,在单线程情况下,还能按照业务的调用顺序去排查,但是在多线程情况下,日志可能存在乱序,排查起来非常难受。

解决:

ThreadLocal的用法,除了用于SimpleDateFormate等之外,还有更好的应用场景:在多线程情况的日志跟踪,同一线程或者一次事务的日志打印,将事务的日志保存在线程局部变量当中,在一次API请求的全链路下,均能全局的获取到这个日志的序号,无需侵入你的业务参数中层层传递。

思路:

首先你可能会想到用static的全局变量去记录日志序号,也能达到效果,但是多线程情况下同时对同一个静态变量操作时,无法确保每个线程取出的值是自己放的值。而ThreadLocal可以把变量绑定到到某一线程上,形成多线程都有自己的变量副本。

ThreadLocal的一点小分析:

先看看ThradLocal的源码:
ThradLocalMap的key为弱引用,JVM在GC时会回收,造成key=null,value=ThradLocalMap,造成内存溢出。
TheadlLocal用法之基于SpringAOP的全局日志跟踪_第1张图片

TheadlLocal用法之基于SpringAOP的全局日志跟踪_第2张图片

SpringAOP

此处用到了基于切面的几个注解,在SpringMVC的RequestMapping注解上进行拦截。

@Pointcut:切入点
@Before:前置通知
@After:后置通知

/**
 * 切面生成处理流程序列号
 **/
@Aspect
@Component
@Slf4j
public class RestSeqNumAspect {
     

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestMapping() {
     
    }

    @Before("requestMapping()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
     
    	ThreadLocalValueHolder.set(joinPoint.getSignature().getName()+"#"+System.currentTimeMillis()+"#"+Thread.currentThread().getId());
        log.info(" rest request[{}] begin------------------",""+ThreadLocalValueHolder.get());
    }
    @After("requestMapping()")
    public void doAfter(JoinPoint joinPoint) throws Exception {
     
        log.info(" rest request[{}]end------------------",""+ThreadLocalValueHolder.get());
        ThreadLocalValueHolder.set(null);
    }
}

在doAfter中需要将对象置为null,释放内存
ThreadLocal类的声明,调用其set(),get()方法就能获取到当前线程的全局变量

public class ThreadLocalValueHolder {
     
    public static final ThreadLocal<Object> THREAD_LOCAL = new ThreadLocal();

    public ThreadLocalValueHolder() {
     
    }

    public static <T> void set(T value) {
     
        THREAD_LOCAL.set(value);
    }

    public static <T> T get() {
     
        return THREAD_LOCAL.get();
    }
}

效果展示:

如图所示,我们能清楚的知道相同的日志序号是来自同一个请求,笔者只在接口的入口和出口处打印了序号,当然可以在每条日志中调用ThreadLocalValueHolder.get()进行打印,获取到的肯定是当前线程生成的序号。

TheadlLocal用法之基于SpringAOP的全局日志跟踪_第3张图片

总结:

使用ThreadLocal还能用于全局的用户信息,用户session等,把这些通用的变量或者对象从业务实体中抽离出来,减少参数的层层传递,以空间换时间,解决并发下对临界资源的访问。

你可能感兴趣的:(TheadlLocal用法之基于SpringAOP的全局日志跟踪)