springboot在feign和线程池中使用TraceId日志链路追踪(最终版)-2

简述

书接前文:SpringBoot使用TraceId日志链路追踪-1

在上文中我们使用了springboot+MDC+Log4j2支持链路ID打印,但是如果我们使用feigin调用其他服务,线程池如何获取链路id呢;这里我们要用到阿里的TTL,在多线程面试中也会常用问到一个问题那就是线程传递问题。刚好这里就实践以下具体用法,并且这也是一个很好的案例。

问题

Spring 默认的日志框架 Logback 中提供的 LogbackMDCAdapter 内部使用的是ThreadLocal,只有本线程才有效,子线程和下游的服务 MDC 里的值会丢失。

主要的难点是解决值传递问题,主要包括以下几个部分:
异步情况下(线程池)如何传递 MDC 中的 TraceId 到子线程
API Gateway 网关中如何传递 MDC 中的 TraceId
微服务之间互相远程调用时如何传递 MDC 中的 TraceId

阿里的TTL组件解决了线程池场景下的上下文传递问题。通过装饰线程池,TTL在任务提交时自动拷贝父线程的上下文到子线程,并在任务结束后清理副本,确保多级线程池调用链路完整。

feign调用时给head加入traceId

1.需要拦截feign的requst请求,并加入自定义的报文头信息
2.需要把这个拦截器配置到feign的配置项中

在application.yml配置文件里面,可以添加feign相关的配置信息,常见的配置信息有如下这些:

loggerLevel:日志级别,四个取值:

  • NONE 不打印日志;
  • BASIC:只打印请求方法、URL、响应状态码、执行时间。
  • HEADERS:打印请求头、响应头的日志信息。
  • FULL:打印所有日志。

连接配置参数

  • connectTimeout:连接超时时间,单位毫秒:ms。
  • readTimeout:读取超时时间,单位毫秒:ms。
  • retryer:重试策略。
  • requestInterceptors:自定义的拦截器,可以多个,是一个List集合。
  • defaultRequestHeaders:默认的请求头信息。
  • defaultQueryParameters:默认的查询参数信息。
  • followRedirects: 是否允许重定向。

这里我们开启HEADERS级别的日志,方便本地服务调用时打印报文头我们可以查看是否TraceId信息;


import feign.RequestInterceptor;
import feign.RequestTemplate;
import gyqx.spd.common.constants.GlobalConstants;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.jboss.logging.MDC;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Optional;

/**
 * @version 1.0.0
 * @Date: 2025/3/15 8:49
 * @Description: 自定义feigin请求拦截器加入traceId,甚至我们可以把前端携带的token信息和用户id等报文头也放进来,方便转发给下一个被调用的服务
 */
@Slf4j
public class CustomFeignInterceptor implements RequestInterceptor {
   
   


    @Override
    public void apply(RequestTemplate template) {
   
   
        // TODO 在这里可以实现一些自定义的逻辑,例如:用户认证
        log.info("Feign执行拦截器....");
        Optional.ofNullable(RequestContextHolder.getRequestAttributes()).map(it -> ((ServletRequestAttributes) it).getRequest())
                .ifPresent(it -> {
   
   
                            String traceId = it.getHeader(GlobalConstants.GLOBAL_X_TRACE_ID);

                            if (StringUtil.isEmpty(traceId)) {
   
   
                                traceId = MDC.get(GlobalConstants.GLOBAL_X_TRACE_ID) == null ? null : MDC.get(GlobalConstants.GLOBAL_X_TRACE_ID).toString();
                            }

                            if (StringUtil.isNotBlank(traceId)) {
   
   
                                MDC.put(GlobalConstants.GLOBAL_X_TRACE_ID, traceId);
                                template.header(GlobalConstants.GLOBAL_X_TRACE_ID, traceId);
                            }
                            log.info("Feign执行拦截器traceId={}", traceId);
                        }
                );

    }
}

FeignConfig配置

一般都是全局配置,每个服务引入依赖自动配置生效,当然如果只测试可以在某个feigin上自己加上


import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.C
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值