日志框架之TLog讲解分析

news2024/11/28 18:57:50

文章目录

  • 1 TLog
    • 1.1 引言
    • 1.2 简介
    • 1.3 TLog操作
      • 1.3.1 pom.xml
      • 1.3.2 替换logback配置项
      • 1.3.3 测试
    • 1.4 TLog接入方式
    • 1.5 TLog的基本原理
      • 1.5.1 日志标签
      • 1.5.2 TLogContext
      • 1.5.3 TLogRPCHandler
    • 1.6 第三方框架的适配
      • 1.6.1 异步线程
        • 1.6.1.1 一般异步线程
        • 1.6.1.2 线程池
      • 1.6.2 对RPC框架的支持
        • 1.6.2.1 对Dubbo和Dubbox的支持
        • 1.6.2.2 对OpenFeign的支持
      • 1.6.3 对常用Http框架的支持
      • 1.6.4 对SpringCloud Gateway的支持
      • 1.6.5 对MQ的支持
      • 1.6.6 总结

1 TLog

1.1 引言

随着微服务盛行,很多公司都把系统按照业务边界拆成了很多微服务,在排错查日志的时候,因为业务链路贯穿着很多微服务节点,导致定位某个请求的日志以及上下游业务的日志会变得有些困难。

这时候可能有的小伙伴就会想到使用SkyWalkingPinpoint等分布式追踪系统来解决,并且这些系统通常都是无侵入性的,同时也会提供相对友好的管理界面来进行链路Span的查询,但是搭建分布式追踪系统还是需要一定的成本的,所以本文要说的并不是这些分布式追踪系统,而是一款简单、易用、几乎零侵入、适合中小型公司使用的日志追踪框架TLog

1.2 简介

TLog提供了一种最简单的方式来解决日志追踪问题,TLog会自动的对日志进行打标签,自动生成traceId贯穿你微服务的一整条链路,在排查日志的时候,可以根据traceId来快速定位请求处理的链路。

TLog不收集日志,只在原来打印的日志上增强,将请求链路信息traceId绑定到打印的日志上。当出现微服务中那么多节点的情况,官方推荐使用TLog+日志收集方案来解决。当然分布式追踪系统其实是链路追踪一个最终的解决方案,如果项目中已经上了分布式追踪系统,那TLog并不适用。

如下图,是ELK配合TLog,快速定位请求处理的链路的示例。
在这里插入图片描述

TLog官网:https://tlog.yomahub.com
github地址:https://github.com/dromara/TLog

1.3 TLog操作

1.3.1 pom.xml

<dependency>
    <groupId>com.yomahub</groupId>
    <artifactId>tlog-all-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>

1.3.2 替换logback配置项

在这里插入图片描述
到这其实就已经完成了配置。

1.3.3 测试

@RestController
public class Controller {
	private static final Logger logger 
		= LoggerFactory.getLogger(Controller.class);
	@RequestMapping(@*"/test")
	public void test() {
		Logger.info("测试");
	}
}

这里是通过slf4jLoggerFactory获取Logger对象,因为logback适配了slf4j,最终会通过logback来输出日志。
在这里插入图片描述

从这可以看出,11794076298070144 就是本次日志输出的时候生成的一个请求的traceId,在排查日志的时候就可以通过这个traceId去搜索出整个请求的链路日志。

1.4 TLog接入方式

TLog总共提供了三种方式接入项目:

  • Javaagent接入方式
  • 字节码注入方式
  • 日志框架适配器方式

上面案例的接入方式其实是属于日志框架适配器方式,并且是对于Logback框架的适配。TLog除了适配了Logback框架,还适配了Log4j框架和Log4j2框架,项目中可自行选择。

Javaagent接入方式和字节码注入方式相比与日志框架适配器方式对代码的入侵性更小,但是这两种方式仅仅只支持SpringBoot项目,并且相较于日志框架适配器的方式,MDC和异步日志功能并不支持,所以要想完整体验TLog的功能,还是建议选择日志框架适配器方式,日志框架适配器方式其实接入也很快,其实也就是修改一下配置文件的事。

项目环境兼容对比SpringBoot项目自启动非SpringBoot项目自启动SpringBoot项目外置容器非SpringBoot项目外置容器
Javaagent接入方式适合不适合不适合不适合
字节码注入方式适合适合不适合不适合
日志框架适配器方式适合适合适合适合
特性支持对比同步日志MDC异步日志
Javaagent接入方式支持不支持不支持
字节码注入方式支持不支持不支持
日志框架适配器方式支持支持支持

1.5 TLog的基本原理

1.5.1 日志标签

前面在介绍TLog的时候,提到TLog会自动的对你的日志进行打标签,这个标签就是日志标签,一个日志标签最多可以包含如下信息:

  • preApp:接口调用方服务名
  • preHost:接口调用方Host
  • preIp:接口调用方ip
  • currIp:当前服务ip
  • traceId链路id,调用方如果传递就是传递的值,不传递就会重新生成
  • spanId:链路spanId,默认是按照如下labelPattern进行数据拼接生成日志标签,所以默认只打出spanIdtraceId
public static String labelPattern = "<$spanId><$traceId>";

public static String generateTLogLabel(String preApp, String preHost, String preIp,String currIp,String traceId,String spanId)
return labelPattern
		.replace( target: "$preApp",preApp)
		.replace( target: "$preHost",preHost)
       .replace("$preIp",preIp)
       .replace( target:"$currIp", currIp)
	   .replace( target:"$traceId",traceId)
	   .replace( target:"$spanId", spanId);
}

这也就是上面为什么示例中会输出 <0><11794076298070144> 这种格式的原因,前面的0其实就是spanId

如果想改变日志标签输出其它信息或者输出的顺序,只需要在SpringBoot配置文件中配置日志标签的生成样式就行。

tlog.pattern=[$preApp][$preIp][$spanId][$traceId]

1.5.2 TLogContext

public class TLogContext {
	private static boolean enableInvokeTimePrint = false:
	private static boolean hasTLogMDC;
	private static boolean hasLogstash;
	private static final TransmittableThreadLocal<String> traceIdTL = new TransmittableThreadLocalo();
	private static final TransmittableThreadLocal<String> preIvkAppTL = new TransmittableThreadlocalo();
	private static final TransmittableThreadlocal<String> preIvkHostTL = new TransmittableThreadlocalo();
	private static final TransmittableThreadLocal<String> preIpTl = new TransmittableThreadLocalo();
	private static final TransmittableThreadLocal<String> currIpTL = new TransmittableThreadLocalo();
	public static void putTraceId(string traceId) {
		traceIdTL.set(traceId);
	}

TLogContextTLog是一个核心的组件,这个组件内部是使用了TransmittableThreadLocal来传递traceIdpreApp等信息。

当有一个请求过来的时候,会从解析出traceIdpreApp等信息,然后设置到TransmittableThreadLocal中,之后就可以在整个调用链路中从TLogContext中获取到traceId等信息。

1.5.3 TLogRPCHandler

在这里插入图片描述
这个组件是用来处理调用方传递的traceIdpreApp等信息,设置到TLogContextMDC中,同时根据日志标签的格式生成日志标签。

1.6 第三方框架的适配

在实际项目中,一个请求处理过程可能会出现以下情况

  • 异步线程处理
  • 跨服务调用
  • MQ调用

那么对于这些情况来说,traceId应该需要在异步线程、跨服务、MQ等中传递,以便更好地排查一个请求的处理链路。

TLog对于以上可能出现的情况都做了大量的适配,保证traceId能够在异步线程、微服务间、MQ等中能够正确传递

1.6.1 异步线程

1.6.1.1 一般异步线程

所谓的一般异步线程就是指直接通过new Thread的方法来创建异步线程,然后来执行,这种方式TLog是天然支持携带traceId

@RestController
public class Controller {
	private static final Logger logger = LoggerFactory.getLogger(Controller.class);
	@RequestMapping("/test")
	public void test() {
		Logger.info("tomcat线程执行");
		new Thread(() -> Logger.info("一般异步线程执行")).start();
	}
}

执行结果
在这里插入图片描述

从这可以看出这种异步方式的确成功传递了traceId

1.6.1.2 线程池

对于线程池来说,其实默认也是支持传递traceId,但是由于线程池中的线程是可以复用了,为了保证线程间的数据互不干扰,需要使用TLogInheritableTask将提交的任务进行包装。

ThreadPoolExecutor pool =
        new ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
pool.execute(new TLogInheritableTask() {
    @Override
    public void runTask() {
      logger.info("异步执行");
    }
});

上述代码的写法会有点耦合,每次提交任务都需要创建一个TLogInheritableTask,比较麻烦,可以按如下写法进行简化。

public class TLogThreadPoolExecutor extends ThreadPoolExecutor{
	//省去构造方法的填充
	@Override
	public void execute(Runnable command{
		super.execute(wrapIfNecessary(command));
	}
	private TLogInheritableTask wrapIfNecessary(Runnable command) {
		if(command instanceof TLogInheritableTask) {
			return (TLogInheritableTask) command;
		}
		return new TLogInheritableTask() {
			@Override
			public void runTask() {
				command.run();
			}
		};
	}
}

自己写个TLogThreadPoolExecutor继承ThreadPoolExecutor,重写execute方法(submit最终也会调用execute方法执行),然后将提交的任务统一包装成TLogInheritableTask,这样需要使用线程池的地方直接创建TLogThreadPoolExecutor就可以了,就不需要在提交任务的时候创建TLogInheritableTask了。

ThreadPoolExecutor pool =
        new TLogThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
pool.execute(() -> logger.info("异步执行"));

1.6.2 对RPC框架的支持

除了对异步线程的支持,TLog也支持常见的DubboDubboxOpenFeign三大RPC框架,在SpringBoot项目中不需要任何配置,只需要引入依赖就可以实现traceId在服务之间的传递

1.6.2.1 对Dubbo和Dubbox的支持

对于DubboDubbox的支持是基于DubboFilter扩展点来的
在这里插入图片描述
TLog通过SPI机制扩展Filter,在消费者发送请求前从TLogContext获取到traceId,然后将traceId和其它调用者数据设置请求数据中,服务提供者在处理请求的时候,也会经过Filter,从请求中获取到traceId等信息,然后设置到TLogContext中,从而实现了traceIddubbo的消费者和提供者之间的传递。

1.6.2.2 对OpenFeign的支持

对于OpenFeign的支持其实也是通过Feign提供的扩展点RequestInterceptor来实现的

public class TLogFeignFilter implements RequestInterceptor{
	private static final Logger log = LoggerFactory.getLogger(TLogFeignFilter.class);
	@Value("${spring.application.name}")
	private String appName;
	@Override
	public void apply(RequestTemplate requestTemplate) {
		String traceId = TLogContext.getTraceId();
		if(StringUtils.isNotBlank(traceId)){	
			requestTemplate.header(TLogConstants.TLOG_TRACE_KEY,traceId);
			requestTemplate.header(TLogConstants.TLOG_SPANID_KEY,SpanIdGenerator.generateNextSpanId);
			requestTemplate.header(TLogConstants.PRE_IVK_APP_KEY,appName);			
			requestTemplate.header(TLogConstants.PRE_IVK_APP_HOST,LocalhostUtil.getHostName());
			requestTemplate.header(TLogConstants.PRE_IP_KEY, LocalhostUtil.getHostIp());
		}else{
			log .debug("[TLOG]本地threadLocal变量没有正确传递traceId,本次调用不传递traceId");
		}
	}
}

发送请求之前,从TLogContext获取到traceId,将traceId等信息添加到请求头中,然后就可以通过Http请求将traceId等信息传递。

当被调用方接收到请求之后,会经过TLogWebInterceptor这个拦截器进行拦截,从请求头中获取到这些参数,设置到TLogContext中。
在这里插入图片描述

1.6.3 对常用Http框架的支持

除了一些RPC框架,TLog也对一些Http框架进行了适配,比如
HttpClient、Okhttp、hutool-http、RestTemplate、forest
使用这些Http框架也可以实现traceId的传递

其实这些框架的适配跟Feign的适配都是大同小异,都是基于这些Http框架各自提供的扩展点进行适配的,将traceId等信息放到请求头中,这里都不举例了,具体的使用方法可以在官网查看。

1.6.4 对SpringCloud Gateway的支持

同样的,TLog也适配了SpringCloud Gateway

public class TLogGatewayFilter implements GlobalFilter, Ordered {
	@Value("${spring.application.name}")
	private String appName;
	private static final Logger log = LoggerFactory.getLogger(TLogGatewayFilter.class);
	@Override
	public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
		return chain.filter(TLogWebFluxCommon.loadInstance().preHandle(exchange, appName)).
		doFinally(signalType -> TLogWebFluxCommon.loadInstance().cleanThreadLocal());
	}
	@Override
	public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; }
}

原理也是一样的,就是适配了GatewayGlobalFilter,从请求头中获取traceId等信息。

除了适配了Gateway网关,TLog也适配了Soul网关。

1.6.5 对MQ的支持

对于MQ的支持跟异步线程差不多,需要将发送的消息包装成TLogMqWrapBean对象

public class TLogMgWrapBean<T> extends TLogLabelBean 
	implements Serializable {
	private static final Logger log = LoggerFactory.getLogger(TLogMqWrapBean.class);
	private static final long serialVersionUID = 1L;
	private T t;
	public TLogMgWrapBean() {}
	public TLogMgWrapBean(T t) {
		this.t = t;
		String traceId = TLogContext.getTraceId();
		
		if (StringUtils.isNotBlank(traceId)) {
			String appName = TLogSpringAware.getProperty("spring.application.name");
			this.setTraceId(traceId);
			this.setPreIvkApp(appName);
			this.setPreIvkHost(Localhostutil.getHostName());
			this.setPreIp(LocalhostUtil.getHostIp());
			this.setSpanId(SpanIdGenerator.generateNextSpanId());
		} else {
			log.warn("[TLOG]本地kafka客户端没有正确传递traceId,本次发送不传递traceId");
		}
}

发送的时候直接发送TLogMqWrapBean对象过去

TLogMqWrapBean<BizBean> tLogMqWrap = new TLogMqWrapBean(bizBean);
mqClient.send(tLogMqWrap);

TLogMqWrapBean会将traceId等信息携带,消费者接受到TLogMqWrapBean,然后通过TLogMqConsumerProcessor处理业务消息。

TLogMqConsumerProcessor.process(tLogMqWrapBean, new TLogMqRunner<BizBean>() {
    @Override
    public void mqConsume(BizBean o) {
     //业务操作
    }
});

如此就实现了traceId通过MQ传递。

在实际使用中,根据不同的MQ的类型,可以将消息包装成TLogMqWrapBean对象的过程和处理消息的过程做统一的封装处理,以减少发送消息和处理消息对于TLog的耦合

1.6.6 总结

其实从上面的各种适配可以看出,其实本质都是一样的,就是根据具体框架的扩展点,在发送请求之前从TLogContext获取到traceId,将traceId等调用者的信息在请求中携带,然后被调用方解析请求,取出traceId和调用者信息,设置到被调用方服务中的TLogContext中。
所以,如果一旦需要遇到官方还未适配的框架或者组件,可以参照上述适配过程进行适配即可。

总的来说,TLog是一款非常优秀的日志追踪的框架,很适合中小公司使用。这里来总结一下TLog的特性:

  • 通过对日志打印标签完成轻量级微服务日志追踪
  • 提供三种接入方式:javaagent完全无侵入接入,字节码一行代码接入,基于配置文件的接入
  • 对业务代码无侵入式设计,使用简单,10分钟即可接入
  • 支持常见的log4jlog4j2logback三大日志框架,并提供自动检测,完成适配
  • 支持dubbo,dubbox,feign三大RPC框架
  • 支持Spring Cloud GatewaySoul网关
  • 支持HttpClientOkhttphttp调用框架标签传递
  • 支持多种任务框架,JDKTimerTaskQuartz,XXL-JOB,spring-scheduled
  • 支持日志标签的自定义模板的配置,提供多个系统级埋点标签的选择
  • 支持异步线程的追踪,包括线程池,多级异步线程等场景
  • 几乎无性能损耗,快速稳定,经过压测,损耗在0.01%

参考链接:https://mp.weixin.qq.com/s/yvzC7KI-nziRunivWee9sA

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

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

相关文章

应用程序性能瓶颈中的CPU缓存优化

1.前言 在应用程序中会有大量的对变量的操作&#xff0c;在一般情况下不会导致问题&#xff0c;但在多线程操作共享变量时&#xff0c;不当的操作会产生大量的冗余操作&#xff0c;造成性能的浪费。这篇文章主要从编码方式与逻辑策略对变量从CPU寄存器&#xff0c;CPU缓存&…

Redis面试题整理

认识Redis 什么是Redis? 一种基于内存的数据库&#xff1b;在内存中完成对数据的读写操作&#xff1b;读写速度非常快&#xff1b;常用于缓存&#xff0c;消息队列&#xff0c;分布式锁等场景 Redis和Memcached有什么区别&#xff1f; 共同点 都是基于内存的数据库&#x…

PaddleNLP系列课程二:RocketQA、SKEP(属性级情感分析)、通用信息抽取技术UIE

文章目录一、使用RocketQA搭建端到端的问答系统1.1 问答系统介绍1.2 RocketQA1.2.1 检索式QA VS预训练时代QA1.2.2 RocketQA简介1.3 使用RocketQA搭建问答系统1.3.1 安装1.3.2 使用预置模型完成预测1.3.3 搭建问答系统1.3.3.1 使用Faiss搭建自己的问答系统1.3.3.2 使用Jina搭建…

Leecode---141、142环形链表

141 难度 &#xff1a; easy 个人主要思路是&#xff0c; 循环遍历每个节点&#xff0c; 判断该节点此前是否被访问过。 方法一&#xff1a; 时间8ms &#xff0c; 内存 6.8M , func hasCycle(head *ListNode) bool {var val map[*ListNode]*ListNode{}if head nil {return …

l2逐笔接口数据传输延时高吗?

l2逐笔接口数据传输延时高吗&#xff1f;信息服务商的机器部署在交易所机房内&#xff0c;并通过接口直接向用户转发。按照交易所的规定&#xff0c;每个接收用户均需支付成本十几万&#xff0c;使用l2逐笔接口数据做量化是需要一定门槛。但用户端SDK直连的方式&#xff0c;能最…

C++学习 Day.9(宏和模板简介)

好久没更了&#xff0c;摆还是爽 遗留问题&#xff1a; (16条消息) int&作为函数返回类型-编程语言-CSDN问答&#xff08;已解决&#xff09; 宏&#xff1a; 预处理器编译指令都以#打头 #define&#xff08;宏常量&#xff09;使得预处理器进行文本替换&#xff0c;而不…

Acwing---795.前缀和

前缀和1.题目2.基本思想3.代码实现4.总结1.题目 输入一个长度为n的整数序列。 接下来再输入m个询问&#xff0c;每个询问输入一对l&#xff0c;r。 对于每个询问&#xff0c;输出原序列中从第l个数到第 r 个数的和。 输入格式 第一行包含两个整数n和m。 第二行包含n个整数&am…

一种简洁又不失优雅的工作流:极狐 flow

本文来自&#xff1a; 万金 极狐(GitLab)解决方案专家 杨周 极狐(GitLab) 高级解决方案架构师 极狐(GitLab) 市场部内容团队 我们提到的 Workflow 是指什么&#xff1f; 我们在日常开发工作中提到的 Workflow 通常是指通过 Git&#xff08;版本控制工具&#xff09;实现的分布式…

JavaSE学习day1_03, Java的发展

5. Java语言的扩展知识,重点 5.1 Java语言的发展 java语言前身是oka语言. JDK5&#xff1a;第一个大版本号更新 JDK8&#xff1a;企业中最常用的版本 JDK17&#xff1a;课程中学习的版本 特点&#xff1a;兼容性。 用jdk8编写的代码&#xff0c;用17可以运行 用jdk17编写…

定位bug

1、bug定位常用工具   Firefox——firebug、web developer、 live http headers、http fox IE插件——httpwatch 第三方工具——fiddler 慢速网模拟工具——firefox throttle 1.该选择框使用来选择资源的&#xff0c;当网页被加载的时候向服务器端请求出来的文件包括.htm…

二维码识别率优化实践

本文字数&#xff1a;5939字预计阅读时间&#xff1a;15 分钟概述长按图片识别二维码在移动端是很常见的操作&#xff0c;长按后需要对图片进行识别&#xff0c;并且将二维码中所包含的数据解码出来。在我们的业务场景中&#xff0c;是通过点击图片进入大图预览页面。长按大图预…

项目管理工具dhtmlxGantt甘特图入门教程(六):dhtmlxGantt的扩展完整列表

dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足项目管理控件应用程序的所有需求&#xff0c;是最完善的甘特图图表库。 这篇文章给大家讲解dhtmlxGantt的扩展完整列表。 DhtmlxGantt正版试用下载&#xff08;qun&#xff1a;764148812&…

【NI Multisim 14.0原理图环境设置——电路总体设计流程】

目录 序言 &#x1f34a;知识点 一、电路板总体设计流程 &#x1f349; 1.创建电路文件 &#x1f349;2.规划电路界面 &#x1f349;3.放置元器件 &#x1f349;4.连接线路和放置节点 &#x1f349;5.连接仪器仪表 &#x1f349;6. 运行仿真并检查错误 &#x1f349;7…

Dropzone4 for MAC 文件拖拽增强工具

前言 ​​Dropzone for mac是一款文件拖拽操作增强工具&#xff0c;可以让我们把大部分工作都通过拖拽来完成&#xff0c;只需将文件拖拽到菜单栏上的窗口即可。比如保存文本、发送邮件、FTP上传、打开应用等等。提高了用户的工作效率。 下载 Dropzone4 特征 -打开应用程序…

连接格式优化,支持自定义

12月&#xff0c; eKuiper 团队继续专注于 1.8.0 版本新功能的开发。我们重构了外部连接&#xff08;source/sink) 的格式机制&#xff0c;更加清晰地分离了连接、格式和 Schema&#xff0c;同时支持了格式的自定义&#xff1b;受益于新的格式机制&#xff0c;我们大幅完善了文…

echarts中formatter修改鼠标悬浮事件信息操作、echarts地图块、散点区分触发点击事件 只触发散点问题详解

这里写目录标题1、实例2、案例详解1、实例 这次我拿echarts中 地图组合散点图的实例 &#xff01;&#xff01;&#xff01;实现效果&#xff1a;滑到散点显示不同于地图块的信息 及 formatter 提示窗自定义&#xff01;&#xff01;&#xff01; 这个显示项目名称为"文昌…

千锋教育+计算机四级网络-计算机网络学习-01

目录 课程链接 最早的广域网 计算机网络发展阶段 计算机网络的定义与要点 英文单词网络术语与解释 计算机网络分类 广域网技术 城域网 局域网 个人局域网 五种基本的网络拓扑结构​ 误码率 电路交换网特点 分组交换 交换方式 TCP/IP协议族 IP协议介绍 TCP协议介绍 …

OpenCV的solvePnP函数和Dlib估计头部姿势

一、姿势估计概述1、概述在许多应用中&#xff0c;我们需要知道头部是如何相对于相机倾斜的。例如&#xff0c;在虚拟现实应用程序中&#xff0c;可以使用头部的姿势来渲染场景的右视图。在驾驶员辅助系统中&#xff0c;在车辆中观察驾驶员面部的摄像头可以使用头部姿势估计来查…

React(coderwhy)- 06(RTK)

认识ReduxToolkit 认识Redux Toolkit ◼ Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。  在前面我们学习Redux的时候应该已经发现&#xff0c;redux的编写逻辑过于的繁琐和麻烦。  并且代码通常分拆在多个文件中&#xff08;虽然也可以放到一个文件管理&#xff0c;…

[RoarCTF 2019]Online Proxy(x-forwarded-for盲注)

这道题点开题目 然后题目显示不出网&#xff0c;一开始误认为是ssrf了&#xff0c;但是没有更多的信息了。 源码有一个ip会不会是修改X-Forwarded-For就可以了呢&#xff0c;抓包试一下&#xff0c; 发现有两个ip&#xff0c;一个当前一个是last上一个的意思把&#xff0c;…