美文网首页apm分布式
MDC skywalking与ThreadLocal

MDC skywalking与ThreadLocal

作者: 修行者12138 | 来源:发表于2023-06-10 11:40 被阅读0次

    MDC

    A Mapped Diagnostic Context, or MDC in short, is an instrument for distinguishing interleaved log output from different sources. Log output is typically interleaved when a server handles multiple clients near-simultaneously.

    The MDC is managed on a per thread basis. Note that a child thread does not inherit the mapped diagnostic context of its parent.

    映射诊断上下文,简称MDC,是一种用于区分来自不同来源的交错日志输出的工具。当服务器几乎同时处理多个客户端时,日志输出通常是交错的。

    MDC基于每个线程进行管理。请注意,子线程不继承其父线程的MDC。

    以上解释来自LogbackMDCAdapter(pom定义如下)

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.7</version>
    </dependency>
    

    ThreadLocal的功能是为当前线程维护一个map

    org.slf4j.MDC基于ThreadLocal,集成了日志组件,具体实现有Log4jMDCAdapter和LogbackMDCAdapter

    org.slf4j.MDC的ThreadLocal里的key,可以在log4j/logback.xml中定义占位符,并输出到日志

    比如,执行MDC.put("traceId", traceId),logback.xml中定义如下,最后输出的日志,就能打印出traceId

    <!--格式化输出:%d表示日期,%thread表示线程名,%traceId表示分布式链路id,%-5level:级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符-->
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%traceId] %-5level - %msg%n</pattern>
    

    至于MDC中的ThreadLocal,有没有跨线程复制的能力(父线程的上下文复制到子线程),与MDCAdapter的版本有关。

    在1.4.7版本的LogbackMDCAdapter,注释写的是Note that a child thread does not inherit the mapped diagnostic context of its parent. 子线程不继承父线程的MDC。

    在1.2.9版本的LogbackMDCAdapter,注释写的是A child thread automatically inherits a copy of the mapped diagnostic context of its parent. 子线程自动继承父线程的MDC。

    Java Instrument && Java Agent

    Java Instrument

    Java Instrument是JDK1.5提供的一个新特性,用于监测JVM进程,甚至可以修改类的实现。有了这样的功能,就可以更灵活的实现运行时虚拟机监控和Java类操作,这样的特性可以看做是一种虚拟机级别的AOP实现方式。

    image.png

    ClassFileTransformer是类文件的转换器

    Instrumentation提供监测Java程序所需的服务

    Java Agent

    Java Agent是一种特殊的Java程序,可以看做是Java Instrumentation的客户端。普通Java程序通过main函数启动,Java Agent无法单独启动,必须依附于一个Java程序上,与他运行在同一个进程中,通过Java Instrumentation的客户端 API与虚拟机交互。

    JVM会把Instrumentation的实例作为参数注入到Java Agent的启动方法上,因此要使用Instrumentation功能,必须通过Java Agent。

    Java Agent有2个加载时机,一个是JVM启动前通过-javaagent参数静态加载执行,另一个是JVM启动后通过Java Tool API中的attach api动态加载执行。

    skywalking

    skywalking的主要作用是以javaAgent的形式(也可以有别的形式),收集应用程序的链路信息(请求入参、响应结果、耗时等等),上传到存储组件,并提供可视化展示。

    源码仓库: https://github.com/apache/skywalking.git

    分支: 6.x

    Trace注解

    方法上添加了@Trace注解,会执行TraceAnnotationMethodInterceptor拦截器,拦截器中会创建span

    package org.apache.skywalking.apm.toolkit.activation.trace;
    
    import net.bytebuddy.description.method.MethodDescription;
    import net.bytebuddy.matcher.ElementMatcher;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.DeclaredInstanceMethodsInterceptPoint;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
    import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
    import org.apache.skywalking.apm.agent.core.plugin.match.MethodAnnotationMatch;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
    
    import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
    import static net.bytebuddy.matcher.ElementMatchers.named;
    
    /**
     * {@link TraceAnnotationActivation} enhance all method that annotated with <code>org.apache.skywalking.apm.toolkit.trace.annotation.Trace</code>
     * by <code>TraceAnnotationMethodInterceptor</code>.
     *
     * @author zhangxin
     */
    public class TraceAnnotationActivation extends ClassInstanceMethodsEnhancePluginDefine {
    
        public static final String TRACE_ANNOTATION_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.toolkit.activation.trace.TraceAnnotationMethodInterceptor";
        public static final String TRACE_ANNOTATION = "org.apache.skywalking.apm.toolkit.trace.Trace";
    
        @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
            return new ConstructorInterceptPoint[0];
        }
    
        @Override public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
            return new InstanceMethodsInterceptPoint[] {
                new DeclaredInstanceMethodsInterceptPoint() {
                    @Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        return isAnnotatedWith(named(TRACE_ANNOTATION));
                    }
    
                    @Override public String getMethodsInterceptor() {
                        return TRACE_ANNOTATION_METHOD_INTERCEPTOR;
                    }
    
                    @Override public boolean isOverrideArgs() {
                        return false;
                    }
                }
            };
        }
    
        @Override protected ClassMatch enhanceClass() {
            return MethodAnnotationMatch.byMethodAnnotationMatch(new String[] {TRACE_ANNOTATION});
        }
    }
    

    TraceCrossThread注解

    类上添加了@TraceCrossThread注解,并且存在名为call, run, get的方法,会执行CallableOrRunnableInvokeInterceptor拦截器,拦截器中会创建span

    package org.apache.skywalking.apm.toolkit.activation.trace;
    
    import net.bytebuddy.description.method.MethodDescription;
    import net.bytebuddy.matcher.ElementMatcher;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
    import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
    import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
    
    import static net.bytebuddy.matcher.ElementMatchers.*;
    import static org.apache.skywalking.apm.agent.core.plugin.match.ClassAnnotationMatch.byClassAnnotationMatch;
    
    /**
     * {@link CallableOrRunnableActivation} presents that skywalking intercepts all Class with annotation
     * "org.skywalking.apm.toolkit.trace.TraceCrossThread" and method named "call" or "run".
     *
     * @author carlvine500 lican
     */
    public class CallableOrRunnableActivation extends ClassInstanceMethodsEnhancePluginDefine {
    
        public static final String ANNOTATION_NAME = "org.apache.skywalking.apm.toolkit.trace.TraceCrossThread";
        private static final String INIT_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.toolkit.activation.trace.CallableOrRunnableConstructInterceptor";
        private static final String CALL_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.toolkit.activation.trace.CallableOrRunnableInvokeInterceptor";
        private static final String CALL_METHOD_NAME = "call";
        private static final String RUN_METHOD_NAME = "run";
        private static final String GET_METHOD_NAME = "get";
    
        @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
            return new ConstructorInterceptPoint[] {
                new ConstructorInterceptPoint() {
                    @Override public ElementMatcher<MethodDescription> getConstructorMatcher() {
                        return any();
                    }
    
                    @Override public String getConstructorInterceptor() {
                        return INIT_METHOD_INTERCEPTOR;
                    }
                }
            };
        }
    
        @Override
        public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
            return new InstanceMethodsInterceptPoint[] {
                new InstanceMethodsInterceptPoint() {
                    @Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        return (named(CALL_METHOD_NAME).and(takesArguments(0)))
                            .or(named(RUN_METHOD_NAME).and(takesArguments(0)))
                            .or(named(GET_METHOD_NAME).and(takesArguments(0)));
                    }
    
                    @Override public String getMethodsInterceptor() {
                        return CALL_METHOD_INTERCEPTOR;
                    }
    
                    @Override public boolean isOverrideArgs() {
                        return false;
                    }
                }
            };
        }
    
        @Override protected ClassMatch enhanceClass() {
            return byClassAnnotationMatch(new String[] {ANNOTATION_NAME});
        }
    
    }
    

    ThreadLocal

    java.lang.ThreadLocal无法复制上下文至子线程

    java.lang.InheritableThreadLocal只有在线程创建时,把父线程的上下文复制到子线程

    阿里开源的com.alibaba.ttl.TransmittableThreadLocal,可以在子线程执行任务前,把父线程的上下文复制到子线程
    pom

    <!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>transmittable-thread-local</artifactId>
        <version>2.12.0</version>
    </dependency>
    

    跨进程传递上下文

    TransmittableThreadLocal只能解决同进程内跨线程复制的问题,如果需要跨进程复制上下文,需要有拦截器。

    以分布式链路id traceId的实现为例,

    比如A服务http请求B服务,可能会通过Feign, RestTemplate, HttpClient等形式。需要为项目中用到的每个形式,定义拦截器,发送http请求时,从MDC取出traceId,放到http header里,key为trace-id。

    B服务也需要有一个拦截器,从http header里拿出trace-id,放到MDC里。

    如果是mq的场景,也是一样的

    生产者需要从MDC取出traceId,放到mq的properties里面

    消费者需要从mq的properties里,拿出traceId,放入MDC

    相关文章

      网友评论

        本文标题:MDC skywalking与ThreadLocal

        本文链接:https://www.haomeiwen.com/subject/hpetydtx.html