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

三:实现效果

相关推荐
程序媛小果几秒前
基于java+SpringBoot+Vue的桂林旅游景点导游平台设计与实现
java·vue.js·spring boot
骑鱼过海的猫1232 分钟前
【java】java通过s3访问ceph报错
java·ceph·iphone
杨充8 分钟前
13.观察者模式设计思想
java·redis·观察者模式
Lizhihao_11 分钟前
JAVA-队列
java·开发语言
喵叔哟20 分钟前
重构代码之移动字段
java·数据库·重构
喵叔哟20 分钟前
重构代码之取消临时字段
java·前端·重构
fa_lsyk23 分钟前
maven环境搭建
java·maven
远望清一色29 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧37 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices41 分钟前
C++如何调用Python脚本
开发语言·c++·python