Spring Boot 監控 API 請求耗時,這八種方案你知道幾個?
環境:SpringBoot3.4.2
1. 簡介
在微服務架構與高并發場景下,API接口的響應速度直接影響用戶體驗與系統穩定性。隨著業務復雜度提升,接口性能問題逐漸成為系統瓶頸,例如數據庫查詢延遲、第三方服務調用超時等場景,均可能導致接口耗時激增。傳統的手動埋點統計方式(如在每個接口方法中記錄開始與結束時間)存在代碼侵入性強、維護成本高的問題,難以滿足大規模接口的監控需求。
本篇文章將介紹 Spring Boot 中記錄 API 請求耗時的 6 種實用方案,涵蓋從基礎到進階的多種實現方式,幫助開發者根據業務場景選擇最適合的監控手段。
2.實戰案例
2.1 手動記錄
我們可以通過Spring內置的StopWatch工具進行記錄方法執行耗時情況:
@GetMapping("/query")
public ResponseEntity<?> query() throws Exception {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 業務邏輯
TimeUnit.MILLISECONDS.sleep(new Random().nextLong(2000)) ;
stopWatch.stop();
System.out.printf("方法耗時:%dms%n", stopWatch.getTotalTimeMillis()) ;
return ResponseEntity.ok("api query...") ;
}
運行結果
方法耗時:1096ms
針對手動記錄這里總結2點缺點:
- 代碼侵入性強:每次需要記錄請求耗時的時候都需要在具體的業務邏輯中插入相應的計時代碼(如使用System.currentTimeMillis()或StopWatch)。這種方式會增加代碼的復雜度,并且使得業務邏輯與性能監控代碼耦合在一起,降低了代碼的可讀性和維護性。
- 重復工作:如果多個地方都需要進行類似的耗時統計,則可能需要在每個地方都添加相同的代碼,這導致了代碼的重復。違反了DRY(Don't Repeat Yourself)原則。
2.2 自定義AOP記錄
通過自定義注解結合Spring AOP切面編程,可實現無侵入式的方法執行耗時統計與記錄。
@Aspect
@Component
public class PerformanceAspect {
private static final Logger logger = LoggerFactory.getLogger("api.timed") ;
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) || "
+ "@annotation(org.springframework.web.bind.annotation.GetMapping) || "
+ "@annotation(org.springframework.web.bind.annotation.PostMapping) || "
+ "@annotation(org.springframework.web.bind.annotation.PutMapping) || "
+ "@annotation(org.springframework.web.bind.annotation.DeleteMapping) || "
+ "@annotation(org.springframework.web.bind.annotation.PatchMapping)")
public Object recordExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch();
sw.start();
Object result = pjp.proceed();
sw.stop();
logger.info("方法【{}】耗時: {}ms", pjp.getSignature(), sw.getTotalTimeMillis()) ;
return result;
}
}
在該示例,我們并沒有自定義注解,而是直接攔截了定義Controller接口使用的注解。
運行結果
Line:29 - 方法【ResponseEntity ApiController.query()】耗時: 487ms
總結:AOP實現耗時記錄非侵入、統一管理、減少重復代碼,適合全局監控。對非Spring管理的方法無效,增加切面復雜度,可能影響性能。
2.3 攔截器技術
通過攔截器不用修改任何業務代碼就能非常方便的記錄方法執行耗時情況。
public class TimedInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
long startTime = (long) request.getAttribute("startTime");
long cost = System.currentTimeMillis() - startTime;
System.out.printf("請求【%s】耗時: %dms%n", request.getRequestURI(), cost) ;
}
}
注冊攔截器
@Component
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimedInterceptor())
.addPathPatterns("/api/**") ;
}
}
運行結果
請求【/api/query】耗時: 47ms
總結:攔截器可集中管理請求耗時記錄,減少代碼侵入性,適用于Controller層統一監控。僅對Web請求(Controller)生效,無法捕獲非HTTP接口或內部方法調用耗時,粒度較粗。
2.4 使用Filter
Filter 是 Servlet 規范中的過濾器,用于在請求到達目標資源前后進行攔截處理,可用于日志記錄、權限校驗等通用邏輯。
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 確保最先執行
public class RequestTimingFilter implements Filter {
private static final PathPatternParser parser = new PathPatternParser();
private static final Logger logger = LoggerFactory.getLogger(RequestTimingFilter.class);
// 從配置文件中讀取排除路徑
@Value("${timing.filter.exclude-paths}")
private String[] excludePaths;
// 路徑匹配器緩存
private List<PathPattern> excludedPatterns = Collections.emptyList();
@Override
public void init(FilterConfig filterConfig) {
// 初始化時編譯排除路徑的正則表達式
excludedPatterns = Arrays.stream(excludePaths).map(path -> parser.parse(path)).toList() ;
logger.info("記錄請求耗時,不記錄的URI: {}", Arrays.toString(excludePaths));
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
// 檢查是否在排除路徑中
if (shouldExclude(requestURI)) {
chain.doFilter(request, response);
return;
}
long startTime = System.nanoTime();
try {
// 執行后續過濾器鏈和實際請求處理
chain.doFilter(request, response);
} finally {
long endTime = System.nanoTime();
long durationNanos = endTime - startTime;
long durationMillis = TimeUnit.NANOSECONDS.toMillis(durationNanos);
// 記錄日志(包含請求方法和狀態碼)
if (response instanceof HttpServletResponse httpResponse) {
int status = httpResponse.getStatus();
logger.info("[{}] {} - {}ms (Status: {})", httpRequest.getMethod(), requestURI, durationMillis, status);
} else {
logger.info("[{}] {} - {}ms", httpRequest.getMethod(), requestURI, durationMillis);
}
}
}
private boolean shouldExclude(String requestURI) {
return excludedPatterns.stream()
.anyMatch(pattern -> pattern.matches(PathContainer.parsePath(requestURI))) ;
}
}
運行結果
RequestTimingFilter Line:77 - [GET] /api/query - 379ms (Status: 200)
總結:Filter 實現耗時記錄非侵入,適用于全局請求監控,配置簡單;僅能記錄整個請求的處理時間,無法精確到具體方法或業務邏輯,粒度較粗。
2.5 通過事件監聽
在Spring MVC底層內部,當一個請求處理完成以后會發布ServletRequestHandledEvent 事件,通過監聽該事件就能獲取請求的詳細信息。
@Component
public class TimedListener {
@EventListener(ServletRequestHandledEvent.class)
public void recordTimed(ServletRequestHandledEvent event) {
System.err.println(event) ;
}
}
運行結果
ServletRequestHandledEvent: url=[/api/query];
client=[0:0:0:0:0:0:0:1]; method=[GET]; status=[200];
servlet=[dispatcherServlet]; session=[null]; user=[null];
time=[696ms]
詳細的輸出了當前請求的信息。
總結:非侵入式獲取請求處理耗時,適用于全局監控且無需修改業務代碼;僅能獲取整個請求的耗時信息,無法定位具體方法或模塊性能問題,粒度較粗。
2.6 Micrometer + Prometheus
Micrometer 是一個指標度量工具,支持多種監控系統,如 Prometheus。通過 @Timed 注解可輕松記錄方法耗時;Prometheus 是開源監控系統,擅長拉取和聚合指標,常與 Micrometer 集成實現可視化監控。兩者結合適合微服務性能觀測。
@Timed(value = "api.query", description = "查詢業務接口")
@GetMapping("/query")
public ResponseEntity<?> query() throws Exception {
TimeUnit.MILLISECONDS.sleep(new Random().nextLong(2000)) ;
return ResponseEntity.ok("api query...") ;
}
在需要監控的接口上添加 @Timed 注解,同時你還需要進行如下的配置:
management:
observations:
annotations:
enabled: true
要結合Prometheus,那么我們還需要引入如下依賴
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置actuator暴露Prometheus端點
management:
endpoints:
web:
exposure:
include: '*'
base-path: /ac
最后,我們還需要在Prometheus中進行配置
- job_name: "testtag"
metrics_path: "/ac/prometheus"
static_configs:
- targets: ["localhost:8080"]
運行結果
圖片
總結:非侵入、集成簡單,支持細粒度方法級監控,與Spring Boot天然兼容,數據可持久化并可視化;需引入監控組件,增加系統復雜度。
2.7 使用Arthas
Arthas 是一款線上監控診斷產品,通過全局視角實時查看應用 load、內存、gc、線程的狀態信息,并能在不修改應用代碼的情況下,對業務問題進行診斷,包括查看方法調用的出入參、異常,監測方法執行耗時,類加載信息等,大大提升線上問題排查效率。
首先,在如下地址下載最新的Arthas
接下來,通過如下命令啟動Arthas
java -jar arthas-boot.jar
執行該命令后,將出現如下選擇
圖片
通過前面的數字選擇哪個進程。
連接到具體的進程后,我們可以通過如下的命令來跟蹤記錄方法執行耗時情況
trace com.pack.timed.controller.ApiController query
運行結果
2.8 使用SkyWalking
SkyWalking 是一個開源的 APM(應用性能監控)系統,支持分布式鏈路追蹤、服務網格觀測、度量聚合與可視化,適用于微服務、云原生和 Service Mesh 架構,幫助開發者實現全棧性能監控與故障診斷。
我們無需在代碼中寫任何代碼或是引入依賴,因為SkyWalking將使用agent技術進行跟蹤系統。
首先,我們需要在如下地址下載SkyWalking已經java對應的agent。
下載下面兩個包
圖片
圖片
下載后,我們可以直接啟動APM,運行bin/startup.bat,
圖片
Web UI 運行在8080端口
最后,我們在運行程序時,需要加入如下JVM參數。
-javaagent:D:\all\opensource\skywalking\skywalking-agent\skywalking-agent.jar
-Dskywalking.agent.service_name=pack-api
訪問http://localhost:8080
圖片
總結:SkyWalking 自動完成鏈路追蹤與耗時監控,支持分布式系統,無需修改代碼,可視化強,性能影響小。在分布式系統中非常推薦。