Java中请求生成唯一追溯TraceId

news2025/1/12 2:49:52

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

image-20240105101009657

image-20240105101208037

三:实现效果

image-20240105100450724image-20240105100530070

image-20240105100556152

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1361723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python中的有序字典是什么

有序字典 一、简介 Python中的字典的特性&#xff1a;无序性。 有序字典和通常字典类似&#xff0c;只是它可以记录元素插入其中的顺序&#xff0c;而一般字典是会以任意的顺序迭代的。 二、普通字典 #! /usr/bin/env python3 # -*- coding:utf-8 -*- d1 {} d1[a] A d1[b…

十种编程语言的对比分析

在当今的软件开发领域&#xff0c;编程语言扮演着至关重要的角色。不同的编程语言各有其特点和适用场景&#xff0c;选择合适的编程语言能够提高开发效率和软件质量。本文将对十种常见的编程语言进行对比分析&#xff0c;帮助读者了解它们的优缺点和适用场景。 一、Python Pyt…

微服务实战系列之API加密

前言 随着一阵阵凛冽寒风的呼啸&#xff0c;新的年轮不知不觉滚滚而来。故事随着2023的远去&#xff0c;尘封于案底&#xff1b;希望迎着新年&#xff0c;绽放于枝头。在2024新岁启航&#xff0c;扬帆破浪之时&#xff0c;让烦恼抛洒于九霄&#xff0c;让生机蓬勃于朝朝暮暮。 …

Java 将Excel转换为TXT文本格式

TXT文件是一种非常简单、通用且易于处理的文本格式。在处理大规模数据时&#xff0c;将Excel转为TXT纯文本文件可以提高处理效率。此外&#xff0c;许多编程语言和数据处理工具都有内置的函数和库来读取和处理TXT文件&#xff0c;因此将Excel文件转换为TXT还可以简化数据导入过…

继续理解Nacos的CP和AP架构模型!

本篇文章延续文章“如何理解Nacos册CP和AP架构模型”&#xff0c;大家可以配套一起学习。 Nacos注册中心处理HTTP注册请求 在文章“如何理解Nacos册CP和AP架构模型”中已经提到过&#xff0c;Nacos注册中心用Restful API InstanceController的方法register()处理HTTP类型的注…

瑞数4——MmEwMD逆向分析

瑞数4——MmEwMD逆向分析 提示简介定位入口MmEwMD生成所需参数分析MmEwMD生成的大致流程第一部分生成(所需三个参数)第一部分生成的值赋值给另一个变量第二部分生成并与第一部分拼接最终的结果与MmEwMD拼接 第一部分生成的逆向分析&#xff08;所需参数&#xff1a;数组a、数字…

Linux第17步_安装SSH服务

secure shell protocol简称SSH。 目的&#xff1a;在进行数据传输之前&#xff0c;SSH先对联级数据包通过加密技术进行加密处理&#xff0c;然后再进行数据传输&#xff0c;确保数据传输安全。 1、在安装前&#xff0c;要检查虚拟机可以上网&#xff0c;否则可能会导致安装失…

C++内容补充--面向对象篇

类和对象 类中静态成员不占用类的大小 也就不占用类的对象的大小 只有一个类壳子的时候 类和对象的大小都是1 当类中只有一个int数据的时候 类和对象的大小都是4 当类中有int数据以及一个静态变量的时候 类和对象的大小还是4 变量存储类以及对象的存储类 所有的局部变量默认…

Linux上传、下载、rz、sz命令

一、上传下载 我们可以通过finalshell工具方便地和虚拟机进行数据交换 在finalshell软件的下方窗体中&#xff0c;提供了Linux的文件系统视图 点击下载就可以下载了 如果想通过root权限&#xff0c;那么需要在编辑器中更换用户名&#xff1a; 上传&#xff1a;直接把windows…

C#,入门教程(08)——基本数据类型及使用的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(07)——软件项目的源文件与目录结构https://blog.csdn.net/beijinghorn/article/details/124139947 数据类型用于指定数据体&#xff08;DataEntity&#xff0c;包括但不限于类或结构体的属性、变量、常量、函数返回值&#xff09;…

架构的本质是什么?

最近总是有小伙伴问我&#xff0c;如何成长为一名优秀的架构师&#xff0c;我也不知道该如何去回答&#xff0c;但是我想聊一下架构的本质。 架构不是互联网行业独有的 架构及对应的架构师职位并不是互联网行业独有的&#xff0c;只要存在组织的地方就存在架构。 比如一个木…

企业出海数据合规:GDPR中的个人数据与非个人数据之区分

GDPR仅适用于个人数据&#xff0c;这意味着非个人数据不在其适用范围内。因此&#xff0c;个人数据的定义是一个至关重要的因素&#xff0c;因为它决定了处理数据的实体是否要遵守该法规对数据控制者规定的各种义务。尽管如此&#xff0c;什么是个人数据仍然是当前数据保护制度…

用通俗易懂的方式讲解:LSTM原理及生成藏头诗(Python)

一、基础介绍 1.1 神经网络模型 常见的神经网络模型结构有前馈神经网络(DNN)、RNN&#xff08;常用于文本 / 时间系列任务&#xff09;、CNN&#xff08;常用于图像任务&#xff09;等等。 前馈神经网络是神经网络模型中最为常见的&#xff0c;信息从输入层开始输入&#xf…

SpringBoot 调用mybatis报错:Invalid bound statement (not found):

启动SpringBoot报错&#xff1a;Invalid bound statement (not found): 参考此文排查 命中了第6条 记录一手坑爹的Invalid bound statement (not found)&#xff08;六个方面&#xff09; mapper文件路径配置错误 订正以后 问题解决

项目从npm迁移到pnpm

场景如下&#xff1a;在安装Vue3时默认为使用Npm安装&#xff0c;如图所示&#xff1a; 安装完后项目就包含了基于NPM的node_modules、package.json&#xff0c;以及package-lock.json 如果想使用pnpm去安装依赖项的话&#xff0c;可以通过如下几个步骤实现&#xff1a; ①删…

基于R语言(SEM)结构方程模型教程

详情点击链接&#xff1a;基于R语言&#xff08;SEM&#xff09;结构方程模型教程 01、R/Rstudio (2)R语言基本操作&#xff0c;包括向量、矩阵、数据框及数据列表等生成和数据提取等 (3)R语言数据文件读取、整理&#xff08;清洗&#xff09;、结果存储等&#xff08;含tidve…

JAVA中小型医院信息管理系统源码 医院系统源码

开发框架&#xff1a;SpringBootJpathymeleaf 搭建环境&#xff1a;jdk1.8idea/eclipsemaven3mysql5.6 基于SpringBoot的中小型医院信息管理系统&#xff0c;做的比较粗糙&#xff0c;但也实现了部分核心功能。 就诊卡提供了手动和读卡两种方式录入&#xff0c;其中IC读卡器使用…

跟随chatgpt从零开始安装git(Windows系统)

为什么我们要安装Git&#xff1f;Git有什么用&#xff1f; 1. 版本控制&#xff1a;Git 可以追踪代码的所有变化&#xff0c;记录每个提交的差异&#xff0c;使您能够轻松地回溯到任何历史版本或比较不同版本之间的差异。 2. 分支管理&#xff1a;通过 Git 的分支功能&#xff…

【C语言:可变参数列表】

文章目录 1.什么是可变参数列表2.可变参数列表的分析与使用2.1使用2.2分析原理2.3分析原码 1.什么是可变参数列表 对于一般的函数而言&#xff0c;参数列表都是固定的&#xff0c;而且各个参数之间用逗号进行分开。这种函数在调用的时候&#xff0c;必须严格按照参数列表中参数…

云卷云舒:【实战篇】Redis迁移

1. 简介 Remote Dictionary Server(Redis)是一个由Salvatore Sanfilippo写的key-value存储系统&#xff0c;是一个开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 2. 迁移原理 redis-sh…