項目拓展-Apache對象池,對象池思想結合ThreadLocal復用日志對象

優化日志對象創建以及日志對象復用

日志對象上下文實體類

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){/…