Java中请求生成唯一追溯TraceId

Java中请求生成唯一追溯TraceId

一:背景

因为是微服务架构,平常日志太多,看日志不太好查,所以想要从一整个链路当中获取一个唯一标识,比较好定位问题,

原理就是从gateway网关将标识传递到下游,下游服务拿到这个标识,响应结束后将traceId反向写入响应体中

二:具体实现

例如我现在有3个微服务,gateway,center-service,注册中心;一个接口完整的请求链路是从gateway路由到center-service中。

1、Gateway

网关处理请求唯一标识

复制代码
package com.wondertek.gateway.traceId;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.UUID;

@Component
@Slf4j
public class TraceIdResponseFilter implements GlobalFilter, Ordered {

    private static final String TRACE_ID_HEADER = "X-Trace-Id";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 在请求前确保traceId存在
        List<String> existingTraceIds = exchange.getRequest().getHeaders().get(TRACE_ID_HEADER);
        String traceId = existingTraceIds != null ? existingTraceIds.get(0) : UUID.randomUUID().toString().replaceAll("-","");
        log.info("网关传递到下游服务的traceId值为:{}", traceId);
        exchange.getRequest().mutate().header(TRACE_ID_HEADER, traceId).build();
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 在响应后添加traceId到响应头
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().add(TRACE_ID_HEADER, traceId);
        }));
    }

    @Override
    public int getOrder() {
        // 响应后执行
        return Ordered.LOWEST_PRECEDENCE;
    }
}

2、center-service

得将此拦截器注册到您的MVC配置中

复制代码
package com.wondertek.web.traceId;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class TraceIdInterceptor implements HandlerInterceptor {
    
    private static final String TRACE_ID_HEADER = "X-Trace-Id";

    /**
     * 这个方法在请求处理之前被调用。在这里,它试图从请求头中获取名为 X-Trace-Id 的值,这通常是一个用于追踪请求在不同微服务之间流转的唯一标识符。
     * 如果这个标识符存在,它会被保存在请求的属性中,这样在后续的处理流程中可以继续访问和使用这个 traceId。
     * @param request
     * @param response
     * @param handler
     * @return
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 提取请求头中的traceId
        String traceId = request.getHeader(TRACE_ID_HEADER);
        // 如果traceId存在,则将其存储到请求中供后续使用
        if (traceId != null) {
            request.setAttribute(TRACE_ID_HEADER, traceId);
        }
        return true;
    }

    /**
     * 这个方法在请求处理之后被调用,但在视图渲染之前执行。
     * 在这里,它从请求属性中获取之前存储的 traceId,然后将这个 traceId 设置在响应头中。
     * 这样做保证了,如果请求中包含了 traceId,那么这个 traceId 将会随着响应返回给发起请求的客户端。
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // 将traceId设置到响应头中,确保返回给调用者
        Object traceId = request.getAttribute(TRACE_ID_HEADER);
        if (traceId != null) {
            response.setHeader(TRACE_ID_HEADER, traceId.toString());
        }
    }

    // 其他方法根据需要实现
    /**
     * afterCompletion方法是Spring MVC的 HandlerInterceptor 接口中的一个回调函数,它在整个请求处理完毕后执行,也就是在视图渲染完毕且响应已经被发送给客户端之后。
     * 这个方法会在请求的最后阶段被调用,通常用于清理资源,记录日志,进行异常处理等操作。
     * 这个方法提供了四个参数:
     * HttpServletRequest:当前的请求对象。
     * HttpServletResponse:当前的响应对象。
     * Object:处理器(通常是一个控制器)。
     * Exception:执行处理器过程中抛出的异常。如果没有异常抛出则为null。
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (ex != null) {
            // 日志中记录异常信息
            log.error("请求处理发生错误,url:{}", ex,request.getServletPath());
        } else {
            // 日志中记录请求处理成功
            log.info("请求处理成功,url:{}",request.getServletPath());
        }
        // 这里还可以根据需要进行其他操作
    }

}

将此拦截器注册到您的MVC配置中

复制代码
package com.wondertek.web.traceId;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class TraceIdWebConfig implements WebMvcConfigurer {

    private final TraceIdInterceptor traceIdInterceptor;

    @Autowired
    public TraceIdWebConfig(TraceIdInterceptor traceIdInterceptor) {
        this.traceIdInterceptor = traceIdInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(traceIdInterceptor);
    }
}

响应请求体中反写traceId

复制代码
package com.wondertek.web.traceId;

import com.wondertek.web.exception.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.bind.annotation.ControllerAdvice;

import java.util.UUID;

@ControllerAdvice
@Slf4j
public class TraceIdResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final String TRACE_ID_HEADER = "X-Trace-Id";

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        //如果只想处理返回类型为Result的控制器方法,您可以在supports方法中做出相应的判断:
        // return returnType.getParameterType().equals(Result.class);

        // 可以进一步细化条件,只处理Result类型的响应

        //这个supports方法的实现总是返回true,这意味着无论返回类型或者转换器类型是什么,当前的ResponseBodyAdvice实现都将被用于所有的响应。
        return true;
    }

    /**
     * 此代码段是ResponseBodyAdvice接口中beforeBodyWrite方法的一个具体实现。此方法允许您在响应体被发送给客户端前对其进行修改。
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 从请求头中提取traceId
        String traceId = request.getHeaders().getFirst(TRACE_ID_HEADER);
        if (body instanceof Result) {
            Result<?> result = (Result<?>) body;
            if (traceId != null) {
                log.info("下游服务接收到的traceId值为:{}", traceId);
                result.setTraceId(traceId);
            } else {
                // 如果服务从请求头中获取不到traceId,生成一个新的
                traceId = UUID.randomUUID().toString().replaceAll("-", "");
                //log.info("下游服务生成的traceId值为:{}", traceId);
                result.setTraceId(traceId);
            }
        }
        return body;
    }
}

Result为自己定义的全局返回对象,其中要加入traceId

三:实现效果

相关推荐
渣哥1 小时前
原来 Java 里线程安全集合有这么多种
java
间彧2 小时前
Spring Boot集成Spring Security完整指南
java
间彧2 小时前
Spring Secutiy基本原理及工作流程
java
Java水解3 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆5 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学6 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole6 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊6 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端
程序员鱼皮6 小时前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码