微服务轮子项目(38) -分布式日志链路跟踪

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 微服务轮子项目(38) -分布式日志链路跟踪

1. 前言

开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用ELK来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿行其中导致很难筛选出指定请求的全部相关日志,以及下游线程/服务对应的日志。

2. 解决思路

解决思路:

  • 每个请求都使用一个唯一标识来追踪全部的链路显示在日志中,并且不修改原有的打印方式(代码无入侵)
  • 使用LogbackMDC机制日志模板中加入traceId标识,取值方式为%X{traceId}

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

3. 方案实现

由于MDC内部使用的是ThreadLocal所以只有本线程才有效,子线程和下游的服务MDC里的值会丢失;所以方案主要的难点是解决值的传递问题,主要包括以几下部分:

  • API网关中的MDC数据如何传递给下游服务
  • 服务如何接收数据,并且调用其他远程服务时如何继续传递
  • 异步的情况下(线程池)如何传给子线程

3.1 修改日志模板

logback配置文件日志格式添加该标识:

3.2 网关添加过滤器

生成traceId并通过header传递给下游服务

3.3 下游服务增加spring拦截器

接收并保存traceId的值:

拦截器

注册拦截器

3.4 下游服务增加feign拦截器

继续把当前服务的traceId值传递给下游服务:

3.5 解决父子线程传递问题

主要针对业务会使用线程池(异步、并行处理),并且spring自己也有@Async注解来使用线程池,要解决这个问题需要以下两个步骤:

3.5.1 重写logback的LogbackMDCAdapter

由于logbackMDC实现内部使用的是ThreadLocal不能传递子线程,所以需要重写替换为阿里的TransmittableThreadLocal

TransmittableThreadLocalAlibaba开源的、用于解决

“在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal扩展。若希望 TransmittableThreadLocal在线程池与主线程间传递,需配合 TtlRunnable

TtlCallable使用。

TtlMDCAdapter类:

package org.slf4j;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.slf4j.spi.MDCAdapter;
public class TtlMDCAdapter implements MDCAdapter {
    /**
     * 此处是关键
     */
    private final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>();
    private static TtlMDCAdapter mtcMDCAdapter;
    static {
        mtcMDCAdapter = new TtlMDCAdapter();
        MDC.mdcAdapter = mtcMDCAdapter;
    }
    public static MDCAdapter getInstance() {
        return mtcMDCAdapter;
    }

其他代码与ch.qos.logback.classic.util.LogbackMDCAdapter一样,只需改为调用copyOnInheritThreadLocal变量

TtlMDCAdapterInitializer类用于程序启动时加载自己的mdcAdapter实现:

public class TtlMDCAdapterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        //加载TtlMDCAdapter实例
        TtlMDCAdapter.getInstance();
    }
}
3.5.2 扩展线程池实现

增加TtlRunnableTtlCallable扩展:

public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    @Override
    public void execute(Runnable runnable) {
        Runnable ttlRunnable = TtlRunnable.get(runnable);
        super.execute(ttlRunnable);
    }
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        Callable ttlCallable = TtlCallable.get(task);
        return super.submit(ttlCallable);
    }
    @Override
    public Future<?> submit(Runnable task) {
        Runnable ttlRunnable = TtlRunnable.get(task);
        return super.submit(ttlRunnable);
    }
    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        Runnable ttlRunnable = TtlRunnable.get(task);
        return super.submitListenable(ttlRunnable);
    }
    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        Callable ttlCallable = TtlCallable.get(task);
        return super.submitListenable(ttlCallable);
    }
}

4. 场景测试

1.测试代码如下:

2. api网关打印的日志:网关生成traceId值为13d9800c8c7944c78a06ce28c36de670

3. 请求跳转到文件服务时打印的日志:显示的traceId与网关相同,这里特意模拟发生异常的场景

4.ELK聚合日志通过traceId查询整条链路日志: 当系统出现异常时,可直接通过该异常日志的traceId的值,在日志中心中询该请求的所有日志信息

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
9天前
|
安全 开发工具 git
git分布式版本控制系统及在码云上创建项目并pull和push
通过本文的介绍,我们详细讲解了Git的基本概念和工作流程,并展示了如何在码云上创建项目及进行pull和push操作。Git作为一种分布式版本控制系统,为开发者提供了强大的工具来管理代码变更和协作开发。希望本文能帮助您更好地理解和使用Git及码云,提高开发效率和代码质量。
37 18
|
8天前
|
安全 开发工具 git
git分布式版本控制系统及在码云上创建项目并pull和push
通过本文的介绍,我们详细讲解了Git的基本概念和工作流程,并展示了如何在码云上创建项目及进行pull和push操作。Git作为一种分布式版本控制系统,为开发者提供了强大的工具来管理代码变更和协作开发。希望本文能帮助您更好地理解和使用Git及码云,提高开发效率和代码质量。
29 16
|
5月前
|
消息中间件 监控 开发工具
微服务(三)-实现自动刷新配置(不重启项目情况下)
微服务(三)-实现自动刷新配置(不重启项目情况下)
|
2月前
|
运维 监控 Cloud Native
一行代码都不改,Golang 应用链路指标日志全知道
本文将通过阿里云开源的 Golang Agent,帮助用户实现“一行代码都不改”就能获取到应用产生的各种观测数据,同时提升运维团队和研发团队的幸福感。
206 12
|
2月前
|
存储 运维 数据可视化
如何为微服务实现分布式日志记录
如何为微服务实现分布式日志记录
136 1
|
5月前
|
NoSQL Java Redis
面试官:项目中如何实现分布式锁?
面试官:项目中如何实现分布式锁?
130 6
面试官:项目中如何实现分布式锁?
|
4月前
|
存储 数据采集 分布式计算
Hadoop-17 Flume 介绍与环境配置 实机云服务器测试 分布式日志信息收集 海量数据 实时采集引擎 Source Channel Sink 串行复制负载均衡
Hadoop-17 Flume 介绍与环境配置 实机云服务器测试 分布式日志信息收集 海量数据 实时采集引擎 Source Channel Sink 串行复制负载均衡
82 1
|
5月前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
389 1
|
3月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
946 31
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
2月前
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。