MDC skywalking与ThreadLocal

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容

  • 大家好久不见,我是问北。今天给大家带来一个日志方面的知识——MDC,不知道大家认识不,反正我是最近刚知道的 初见M...
    BiggerBoy阅读 513评论 0 0
  • MDC 简介 MDC ( Mapped Diagnostic Contexts ),它是一个线程安全的存放诊断日志...
    xiaolyuh阅读 24,176评论 0 14
  • 前言 在做分布式链路追踪系统的时候,需要解决异步调用透传上下文的需求,特别是传递traceId,本文就线程池透传几...
    猿必过阅读 1,096评论 0 0
  • 一、目的 开发排查系统问题用得最多的手段就是查看系统日志,但是在分布式环境下使用日志定位问题还是比较麻烦,需要借助...
    zlt2000阅读 767评论 0 3
  • 一、项目背景 2017年,vivo互联网研发团队认为调用链系统对实际业务具有较大的价值,于是开始了研发工作。3年的...
    vivo互联网技术阅读 580评论 0 0