优化日志对象创建以及日志对象复用

日志对象上下文实体类

traceId

请求到达时间戳

请求完成时间戳

请求总共耗费时长

get/post/put/delete请求方式

Http状态码

原始请求头中的所有键值对

请求体内容

响应体内容

失败Exception信息详细记录

是否命中缓存

package com.kira.scaffoldmvc.CommonPool;import lombok.Data;
import lombok.experimental.Accessors;/*** RTA代理上下文:存储请求处理全流程的关键信息* 设计特点:* - 线程隔离:通过ThreadLocal管理,确保每个请求独立使用* - 链式调用:通过@Accessors(chain = true)支持方法链风格* - 全量数据:包含请求、响应、转发和缓存等完整生命周期信息*/
@Data
@Accessors(chain = true)
public class RtaProxyContext {/*** 全局唯一请求ID(TraceID)* 用于全链路追踪,关联请求与响应日志*/private String reqXid;/*** 请求到达时间戳(毫秒)* 用于计算请求处理耗时*/private long reqTime;/*** 请求路径*/private String url;/*** HTTP请求方法(GET/POST等)*/private String reqType;/*** 请求头信息* 存储原始HTTP请求头的键值对*/private Object reqHeaders;/*** 请求体内容* 通常为JSON格式的广告请求参数*/private Object reqBody;/*** 响应体内容* 最终返回给客户端的内容*/private Object respBody;/*** HTTP响应状态码* 如200、403、500等*/private Integer respCode;/*** 响应完成时间戳(毫秒)* 用于计算总处理耗时:respTime - reqTime*/private long respTime;/*** 错误详情* 当请求处理过程中发生异常时记录*/private String errorDetails;/*** 请求体填充率* 广告请求中有效流量占比,范围0.0-1.0*/private double reqBodyFillRate;/*** 是否命中缓存标识* true表示响应数据来自缓存而非实时计算*/private Boolean cached = false;}

RtaProxyContextHolder-对象池定义与封装

最小空闲对象数:200(系统启动的时候预热)

最大空闲对象数:600

最大对象数:1000

setMaxTotal(1000):池内对象的最大数量(活跃 + 空闲)

setMaxIdle(600):最大空闲对象数,超过此数量的空闲对象将被销毁

setMinIdle(200):最小空闲对象数,池会自动维持此数量的空闲对象

package com.kira.scaffoldmvc.CommonPool;import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;//ThreadLocal结合对象池,封装了对象池的相关操作
@Slf4j
public class RtaProxyContextHolder {private static final ThreadLocal<RtaProxyContext> CONTEXT_HOLDER = new ThreadLocal<>();public static final GenericObjectPool<RtaProxyContext> RTA_PROXY_CONTENT_POOL = new GenericObjectPool<>(new RtaProxyContextFactory(),//对象池工厂new GenericObjectPoolConfig<>() {{//对象池配置setMaxTotal(1000);//最大对象数setMaxIdle(600);//最大空闲对象数setMinIdle(200);//最小空闲对象数}});//自定义对象池工厂private static class RtaProxyContextFactory extends BasePooledObjectFactory<RtaProxyContext> {//创建新对象@Overridepublic RtaProxyContext create() {return new RtaProxyContext();}//负责将新创建或从其他地方获取的 RtaProxyContext 对象包装成 PooledObject<RtaProxyContext> 类型的对象// 这里使用 DefaultPooledObject 进行包装,以便对象池进行统一管理//将其他地方的对象(例如不是对象池创建的对象)转换成对象池对象实现复用@Overridepublic PooledObject<RtaProxyContext> wrap(RtaProxyContext context) {return new DefaultPooledObject<>(context);}@Overridepublic void destroyObject(PooledObject<RtaProxyContext> p) {// 销毁对象的逻辑}}public static RtaProxyContext getContext() {return CONTEXT_HOLDER.get();}public static RtaProxyContext borrowContext() {RtaProxyContext rtaProxyContext;try {//先从对象池获取对象rtaProxyContext = RTA_PROXY_CONTENT_POOL.borrowObject();} catch (Exception e) {//对象池获取对象失败,为了保证对象池可以成功创建,所以要new一个对象rtaProxyContext = new RtaProxyContext();log.warn("Failed to pull from pool");}//将详细的日志对象放到ThreadLocal里面CONTEXT_HOLDER.set(rtaProxyContext);return rtaProxyContext;}public static void returnContext(RtaProxyContext context) {//将日志对象从ThreadLocal中移除CONTEXT_HOLDER.remove();//将原本的日志对象的大部分值变成空值if (context != null) {try {context.setReqXid(null);context.setReqTime(-1);context.setReqType(null);context.setReqHeaders(null);context.setReqBody(null);context.setRespBody(null);context.setRespCode(-1);context.setRespTime(-1);context.setErrorDetails(null);context.setReqBodyFillRate(-1);context.setCached(false);RTA_PROXY_CONTENT_POOL.returnObject(context);} catch (Exception e) {log.warn("Failed to return to pool");}}}//日志打印对象池的状态public static void printPoolStatus() {log.info("Context Pool状态, 活跃对象数 {}, 空闲对象数 {}, 等待获取对象的线程数 {}, 对象创建总次数 {}, 对象销毁总次数 {}.",RTA_PROXY_CONTENT_POOL.getNumActive(),RTA_PROXY_CONTENT_POOL.getNumIdle(),RTA_PROXY_CONTENT_POOL.getNumWaiters(),RTA_PROXY_CONTENT_POOL.getCreatedCount(),RTA_PROXY_CONTENT_POOL.getDestroyedCount());}// 动态调整对象池配置public static void adjustPoolConfig(int maxTotal, int maxIdle, int minIdle) {GenericObjectPoolConfig<RtaProxyContext> config = new GenericObjectPoolConfig<>();config.setMaxTotal(maxTotal);config.setMaxIdle(maxIdle);config.setMinIdle(minIdle);// 应用新配置RTA_PROXY_CONTENT_POOL.setConfig(config);log.info("对象池配置已更新: maxTotal={}, maxIdle={}, minIdle={}", maxTotal, maxIdle, minIdle);}}

RtaProxyContextHolder-请求拦截器

package com.kira.scaffoldmvc.CommonPool;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;import java.util.Collections;
import java.util.stream.Collectors;/*** HTTP请求拦截器:管理请求生命周期内的上下文对象(RtaProxyContext)* 主要功能:* 1. 请求进入时从对象池获取上下文对象并初始化* 2. 请求处理过程中通过ThreadLocal存储上下文* 3. 请求结束后记录日志并归还对象到池** 线程安全机制:* - 使用ThreadLocal确保每个请求线程拥有独立的上下文实例* - 对象池复用机制减少频繁创建/销毁对象的开销*/
@Component
@RequiredArgsConstructor
public class RtaProxyContextInterceptor implements HandlerInterceptor {private final LogService logService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. 从对象池获取可复用的上下文对象//    若池为空则创建新对象,避免频繁new操作带来的GC压力RtaProxyContext rtaProxyContext = RtaProxyContextHolder.borrowContext();// 2. 初始化上下文:设置请求基础信息rtaProxyContext.setReqTime(System.currentTimeMillis())//请求时间戳.setReqXid(request.getHeader("traceId"))// 全局唯一请求ID(traceId).setReqType(request.getMethod())// HTTP方法(GET/POST等).setReqHeaders(Collections.list(request.getHeaderNames())//请求头信息.stream().collect(Collectors.toMap(name -> name, request::getHeader))).setUrl(request.getRequestURI());//请求url// 3. 放行请求继续处理链return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 1. 从当前线程获取上下文对象//    确保与preHandle中设置的是同一个实例RtaProxyContext rtaProxyContext = RtaProxyContextHolder.getContext();// 2. 补充响应信息:状态码、响应时间、异常详情rtaProxyContext.setRespCode(response.getStatus())               // HTTP响应状态码.setRespTime(System.currentTimeMillis())                   // 响应完成时间.setErrorDetails(ex != null ? ex.getMessage() : null);    // 异常信息(若有)// 3. 针对特定API路径记录详细日志//    注意:此处硬编码路径需根据实际业务调整if ("/api/v1/aff_rta".equals(request.getRequestURI())) {//因为该日志是打印对象信息,所以封装了对应的实现类logService.logRtaAccess(RtaAccessLog.from(rtaProxyContext));     // 记录访问日志}// 4. 关键资源回收步骤://    - 从ThreadLocal中移除引用,防止内存泄漏//    - 重置对象状态并归还到对象池供后续请求复用RtaProxyContextHolder.returnContext(rtaProxyContext);}
}

定时任务-打印对象池状态

package com.kira.scaffoldmvc.CommonPool;import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class SysMonitorConfig {@Scheduled(fixedRateString = "${sys.monitor.pool.rate:300000}")public void monitorContextPool() {RtaProxyContextHolder.printPoolStatus();}}

定时任务-自适应调整对象池参数

package com.kira.scaffoldmvc.CommonPool;import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;import static com.kira.scaffoldmvc.CommonPool.RtaProxyContextHolder.RTA_PROXY_CONTENT_POOL;@Component
public class SysMonitorConfig {// 滑动窗口配置private final ArrayList<Integer> list = new ArrayList<>();//最大修改范围是原来对象池范围的两倍或三倍private final Integer minIdle = 600;private final Integer maxIdle = 1800;private final Integer maxTotal = 3000;//每五分钟打印一次对象池状态,并将当前活跃对象数放进List中@Scheduled(cron = "0 0/5 * * * *")public void monitorContextPool() {RtaProxyContextHolder.printPoolStatus();list.add(RTA_PROXY_CONTENT_POOL.getNumActive());}//每半小时统计后5个滑动窗口里的对象数,如果平均活跃对象数大于当前对象池中配置的最大空闲对象数,则更新@Scheduled(cron = "0 0/30 * * * *")public void adjustContextPool() {Integer averageIdle = 0;Integer sum = 0;for (int i = list.size() - 1, j = 0; j < 6; i--, j++) {sum += list.get(i);}averageIdle = sum / 6;if(averageIdle>RTA_PROXY_CONTENT_POOL.getMaxIdle() && RTA_PROXY_CONTENT_POOL.getMaxIdle()!=maxIdle){//平均活跃数大于对象池最大空闲对象数,则进行更新,如果对象池最大空闲对象数已经更新到了可达到的最大值,那么就没必要更新int minSize = 200;if(averageIdle/2>RTA_PROXY_CONTENT_POOL.getMaxIdle()){//如果平均活跃数/2大于最小活跃数,则直接更新成允许范围的最大值minSize=minIdle;}int maxSize = RTA_PROXY_CONTENT_POOL.getMaxIdle();while(maxSize<averageIdle){maxSize += 600;}if(maxSize>maxIdle){maxSize=maxIdle;}//更新对象池配置RtaProxyContextHolder.adjustPoolConfig(maxTotal,maxSize,minSize);}}}

如何分析响应速度

响应速度:拿出之前没使用对象池时url的平均时间和使用对象池后url的平均时间进行对比


总结

因为不同的业务要用到不同的信息,所以将一个请求的信息封装到对象里面,这样子可以应付许多不同的日志打印场景或其他需要使用请求信息的业务

例如我们把

traceId

请求到达时间戳

请求完成时间戳

请求总共耗费时长

get/post/put/delete请求方式

Http状态吗

原始请求头中的所有键值对

请求体内容

响应体内容

失败Exception信息详细记录

是否命中缓存

封装到对象里面,日志信息非常详细,可以更加方便排查问题,操作对象也可以使获取信息更简单

定时任务,每五分钟打印对象对象池的状态

定时任务,每30分钟检查之前的平均活跃对象数是否大于对象池中的最大空闲对象数,如果大于则通过自适应策略对对象池容量进行一个自适应更新

通过对比不同接口使用对象池前后的用时时间,计算出提高了多少的响应时间

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Javaweb - Vue入门

Vue是一款用于构建用户界面的渐进式的JavaScript框架。 使用步骤 引入Vue模块&#xff0c;创建Vue的应用实例&#xff0c;定义元素&#xff0c;交给Vue控制。 一、引入Vue模块 因为使用的是模块化的JavaScript&#xff0c;因此在script标签内要声明一个属性&#xff1a;typ…

C++ 标准模板库各个容器的应用场景分析

C 标准模板库&#xff08;STL&#xff09;中的容器分为序列式容器、关联式容器和无序容器&#xff0c;各自适用于不同场景。以下是主要容器的应用场景及案例&#xff1a; 一、序列式容器 元素按插入顺序存储&#xff0c;支持线性访问。 1. vector 场景&#xff1a;动态数组…

安装前端vite框架,后端安装fastapi框架

前期准备 首先新建一个文件夹&#xff0c;文件夹里面新建一个文件夹&#xff0c;用于安装依赖 安装vite框架 npm init -y 目的是安装package.json配置文件 npm install vite --save-dev 安装vite框架 安装完是这个样子 新建了一个文件夹和js文件 后端内容 main.js document.…

深度学习:基础与概念(第1章:深度学习革命)

目录 第1章&#xff1a;深度学习革命 1.1深度学习的影响 1.1.1医疗诊断 1.1.2蛋白质结构预测 1.1.3图像合成 1.1.4大语言模型 1.2一个教学示例 1.2.1合成数据 1.2.2线性模型 1.2.3误差函数 1.2.4模型复杂度 1.2.5正则化 1.2.6模型选择 1.3机器学习简史 1.3.1单层…

通过触发器统计访问数据库的客户端IP地址

通过触发器统计访问数据库的客户端IP地址 创建用户登录审计表创建登录审计触发器查看登录审计结果禁用和启用触发器创建用户登录审计表 创建记录表: create table appuser1.user_login_audit (login_time DATE,session_id number,username VARCHAR2(30),os_user VARCHAR2(30…

在MCU上的1微秒的延迟实现方案及测量方法

运行环境&#xff1a; stm32h743iit6; 主频480MHz; APB1; 240MHz; TIM5 240MHz; 预分频系数为1; 定时器计数频率240MHz&#xff1b; 应用需求&#xff1a;实现软件模拟IIC&#xff0c;延迟精度2个微秒&#xff1b; 量变引起质变&#xff0c;当延迟粒度太小时&#xff0c;需要考…

macos电脑本地搭建mistral-7b大模型出现4-bit量化和缓存不足问题的记录

问题背景 本人想再本地笔记本电脑上搭建一个mistral-7b的大模型&#xff0c;在搭建的过程中&#xff0c;出现了4-bit量化模式无法处理的问题&#xff0c;以及电脑内存/显存不足的问题&#xff0c;导致无法搭建 电脑硬件信息 名称&#xff1a;2019 Mac book pro 内存&#xff1a…

C# 基础知识总结(带详细文字说明)

1. 基础语法结构 C# 程序由命名空间、类和方法组成。每个程序必须有一个 Main 方法作为入口点。using 指令用于导入命名空间&#xff0c;Console.WriteLine() 是常用的输出方法。 csharp 复制 下载 using System; // 引入核心命名空间class Program // 类定义 {static v…

C#最佳实践:为何要统一命名

C#最佳实践:为何要统一命名 在 C# 编程的世界里,代码就像是一座庞大的数字城市,而命名则是城市中纵横交错的街道名称与建筑标识。如果没有统一的命名规范,这座城市将陷入混乱,开发者在其中探索、维护代码时也会迷失方向。统一命名不仅是一种编程习惯,更是保障代码质量、…

通过后端连接Opengauss数据库的方法

文章目录 通过后端连接Opengauss数据库的方法一、为什么默认不能访问&#xff1f;二、要让普通用户从宿主机访问数据库&#xff0c;需要以下几个步骤&#xff1a;1. 使用 omm 超级用户登录数据库2. 创建一个应用程序专用用户&#xff0c;并设置密码3. 提供给应用程序专用用户对…

AWS Config:概述、优势以及如何开始?

在当今云原生架构快速发展的背景下&#xff0c;越来越多企业意识到资源配置管理和合规性审查的重要性。作为 AWS 官方授权代理商&#xff0c;在云上致力于为企业客户提供全面、可靠的云服务解决方案&#xff0c;帮助企业轻松上云、合规运营。本文将为您详细解读 AWS Config ——…

金融领域LLM开源测试集

BizFinBench 中文 金融业务场景基准数据集 结合迭代校准评估框架IteraJudge&#xff0c;对25个先进LLM进行全面评估&#xff0c;发现在金融AI领域与人类期望存在显著性能差距。 https://arxiv.org/pdf/2505.19457 https://github.com/HiThink-Research/BizFinBench/tree/m…

跨语言RPC:使用Java客户端调用Go服务端的JSON-RPC服务

在分布式系统开发中&#xff0c;不同编程语言之间进行通信是一个常见的需求。通过远程过程调用&#xff08;RPC&#xff09;技术&#xff0c;我们可以让不同的程序像调用本地方法一样调用远程的服务。本文将介绍如何使用Go语言编写一个简单的JSON-RPC服务&#xff0c;并使用Jav…

UE5 创建AI控制器、AI行为树和黑板

UE5 创建AI控制器、AI行为树和黑板 一、创建AI控制器AIController&#xff08;大脑&#xff09; 二、创建AI行为树和黑板 1&#xff1a;AI人工智能 2&#xff1a;行为树 3&#xff1a;黑板 三、AI行为树蓝图和添加黑板 1&#xff1a;添加黑板&#xff08;脑电波&#xff09;…

CDN加速导致CLS升高图片托管服务器的3个选择标准!

许多网站为了提升加载速度&#xff0c;会采用CDN加速服务分发图片等静态资源 这样做可能导致CLS&#xff08;累积布局偏移&#xff09;指标升高&#xff0c;拖累SEO评分。 这一问题通常源于CDN的异步加载机制或图片尺寸未预定义&#xff0c;使得页面布局在渲染过程中频繁变动。…

MySQL(77)如何设置自动备份任务?

设置自动备份任务可以确保你的数据库定期备份&#xff0c;防止数据丢失。以下是如何使用 Bash 脚本和 Cron 任务在 Linux 系统上设置 MySQL 数据库的自动备份任务的详细步骤和代码示例。 1. 编写备份脚本 首先&#xff0c;我们需要编写一个备份脚本。这个脚本将包含执行备份的…

.NET 开发中全局数据存储的几种方式

文章目录 一、静态类与静态成员实现方式特点优缺点 二、应用程序配置系统1. appsettings.json (ASP.NET Core)使用方式2. 用户设置 (WinForms/WPF)特点 三、依赖注入容器ASP.NET Core 示例特点 四、内存缓存 (IMemoryCache)实现方式特点 五、分布式缓存 (IDistributedCache)实现…

人才争夺战关键期,AI如何赋能招聘效率倍增、精准选拔

数智化转型浪潮席卷全球的今天&#xff0c;人才作为企业核心竞争力的地位日益凸显。而在传统招聘流程&#xff0c;尤其是面试环节正面临效率瓶颈、体验短板等多项挑战&#xff0c;典型如&#xff1a; 耗时冗长的筛选与安排&#xff1b;难以避免的主观评价偏差&#xff1b;海量…

介绍下分布式ID的技术实现及应用场景

什么是分布式ID 分布式ID是指在分布式系统中生成的特定范围内唯一的标识符&#xff0c;如订单号、商品ID、链路追踪TraceID。 随着业务发展&#xff0c;对分布式ID的要求越来越高&#xff0c;其中最基本的要求如下 全局唯一&#xff1a;在任何节点、任何时间生成的ID都必须是…

【leetcode-字母异位词分组】

排序法 public List<List<String>> groupAnagrams(String[] strs) {//最终值List<List<String>> result new ArrayList<>();//排序法HashMap<String,List<String>> map new HashMap<>(); //遍历strfor(String str : strs){/…