Spring cloudSpring Cloud Spring Cloud 核心技术

Spring Cloud 框架使用 Zipkin 打印调用链日志

2019-01-31  本文已影响14人  _晓__

背景

现在使用 Spring Cloud 框架的公司越来越多了,也希望写这篇文章对刚入手 Spring Cloud 的同学有所帮助,对系统做出更好更多的功能。

问题举例

所需知识

如何在子线程和线程池中使用 ThreadLocal 传输上下文

问题解决

打印 traceId 意义

日志中打印 Zipkin traceId

使用 Spring Cloud 框架整合 Zipkin 特别方便,只需要在 maven pom 文件中配置 spring-cloud-sleuth-zipkin-stream(还需依赖其他 pom,可自行百度),再到 logback-spring.xml 文件中配置日志格式模板,Zipkin 默认 traceId 名称为 X-B3-TraceId

<property name="log.console_log_pattern" value="[%date] %clr([%level]) [%thread] [traceId:%clr(%X{X-B3-TraceId}){blue}] %clr([%logger]:%L){cyan} >>> %msg %n"/>

子线程或线程池中获取 Zipkin traceId 并打印

经过阅读 Spring Cloud Sleuth 源码,发现 Zipkin 使用 ThreadLocal 来存储 traceId,只能在当前线程获取,无法子线程传递或线程池传递,获取需要改造 Zipkin 使用 TransmittableThreadLocal 存储 traceId,对 TransmittableThreadLocal 不熟悉的同学,可以看 https://www.jianshu.com/p/4093add7f2cd
通过看源码,发现存储 traceId 的代码逻辑在 SpanContextHolder

class SpanContextHolder {
    private static final ThreadLocal<SpanContextHolder.SpanContext> CURRENT_SPAN = new NamedThreadLocal("Trace Context");
}

NamedThreadLocal 继承于 ThreadLocal

public class NamedThreadLocal<T> extends ThreadLocal<T> {
}

然后我们再看哪里调用了 SpanContextHolder 类,我们发现在 DefaultTracer 类中调用了 SpanContextHolder,再看哪里初始化了 DefaultTracer,再追踪到了 TraceAsyncConfiguration

@Configuration
@ConditionalOnProperty(
    value = {"spring.sleuth.enabled"},
    matchIfMissing = true
)
@EnableConfigurationProperties({TraceKeys.class, SleuthProperties.class})
public class TraceAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean({Tracer.class})
    public DefaultTracer sleuthTracer(Sampler sampler, Random random, SpanNamer spanNamer, SpanLogger spanLogger, SpanReporter spanReporter, TraceKeys traceKeys) {
        return new DefaultTracer(sampler, random, spanNamer, spanLogger, spanReporter, this.properties.isTraceId128(), traceKeys);
    }
}

看到这里,发现 DefaultTracer 的创建使用了 @ConditionalOnMissingBean({Tracer.class}) ,那就说明了只要我们自定义一个 TracerTraceAutoConfiguration 中的 DefaultTracer 就不再创建了。

获取 Zipkin traceId 步骤
第一步:
创建自己的 TraceAutoConfiguration 配置类

@Order
@Configuration
@ConditionalOnClass(TraceAsyncAspect.class)
@ConditionalOnProperty(value = {"spring.sleuth.async.enabled", "spring.sleuth.enabled"}, matchIfMissing = true)
@EnableConfigurationProperties({TraceKeys.class, SleuthProperties.class})
public class HtTraceAsyncConfiguration {

    @Autowired
    private SleuthProperties properties;

    @Bean
    public HtTracer sleuthTracer(Sampler sampler, Random random,
                                 SpanNamer spanNamer, SpanLogger spanLogger,
                                 SpanReporter spanReporter, TraceKeys traceKeys) {
        return new HtTracer(sampler, random, spanNamer, spanLogger,
                spanReporter, this.properties.isTraceId128(), traceKeys);
    }
}

第二步:
该配置类里面创建的 Trace 类则是我们自定义类,把原有的 DefaultTracer 拷贝出来改名成我们自定义类名(如上面的 HtTracer),把 HtTracer 类中使用了 SpanContextHolder 替换成自定义的 SpanContextHolder

第三步:
创建自定义的 SpanContextHolder ,拷贝 SpanContextHolder 进行改造,把里面使用的 NamedThreadLocal 替换成自定义的 NamedThreadLocal

class HtSpanContextHolder {
    private static final ThreadLocal<SpanContext> CURRENT_SPAN = new NamedTransmittableThreadLocal<>("Trace Context");
}

第四步:
NamedThreadLocal 拷贝进行改造,继承于 TransmittableThreadLocal 即可。

public class NamedTransmittableThreadLocal<T> extends TransmittableThreadLocal<T> {
}

traceId 在子线程或线程池打印到日志中

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>log4j2-ttl-thread-context-map</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.ofpay</groupId>
    <artifactId>logback-mdc-ttl</artifactId>
    <version>1.0.2</version>
</dependency>

具体使用,可以参考 TTL GitHub:https://github.com/alibaba/transmittable-thread-local

上一篇下一篇

猜你喜欢

热点阅读