前言
线上服务性能排查、流量压测、容量评估,两个核心指标必不可少:
- 当前正在执行业务的请求并发量(仅统计 Controller 业务接口,过滤静态资源、健康检查、Actuator)
- Tomcat 线程池排队等待的请求堆积数量(线程打满后新请求阻塞队列长度,超时预警核心依据)
很多同学只会依赖 Actuator 指标,但存在两个痛点:
- tomcat.threads.busy 统计所有占用 Tomcat 线程的请求(静态资源、WebSocket、异步长连接都会计入,业务并发失真)
- 想本地接口实时查看、自定义告警逻辑,需要代码直接读取 Tomcat 底层线程池队列
本文提供两套无冲突方案:
- SpringMVC 拦截器精准统计业务 Controller 真实并发
- 读取内嵌 Tomcat 底层对象获取排队等待请求数
两套代码可共存,统一监控接口对外输出全量指标。
一、方案 1:拦截器精准统计业务请求并发(推荐,贴合业务视角)
实现原理
基于HandlerInterceptor,只拦截 Controller 业务方法(HandlerMethod):
- preHandle:进入 Controller 逻辑前,并发计数器 + 1
- afterCompletion:请求彻底结束(无论正常 / 异常),计数器 - 1
使用AtomicInteger保证并发计数线程安全,自动过滤静态资源、页面跳转、404 无效路径。
1. 并发统计拦截器
|
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class RequestConcurrencyInterceptor implements HandlerInterceptor {
/**
* 原子计数器:当前正在处理的Controller业务请求并发数
* 仅统计接口方法,静态资源、websocket、过滤器逻辑不计入
*/
private final AtomicInteger concurrentReq = new AtomicInteger(0);
/**
* Controller执行前计数+1
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 只拦截@RestController/@Controller中的方法
if (handler instanceof HandlerMethod) {
concurrentReq.incrementAndGet();
}
return true;
}
/**
* 请求完全处理完毕(正常返回/抛出异常都会执行)计数-1
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (handler instanceof HandlerMethod) {
concurrentReq.decrementAndGet();
}
}
/**
* 对外暴露获取实时业务并发
* @return 当前正在处理的Controller请求数量
*/
public int getCurrentConcurrency() {
return concurrentReq.get();
}
} |
2. 注册拦截器到 SpringMVC
|
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private RequestConcurrencyInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 拦截全部路径,内部自动过滤非Controller请求
registry.addInterceptor(interceptor)
.addPathPatterns("/**");
}
} |
指标说明(和 Tomcat 活跃线程区分)
拦截器getCurrentConcurrency():纯业务接口并发
- 只统计@RequestMapping标注的接口方法
- 静态资源、/actuator 健康检查、WebSocket 长连接不会累加计数
- 异步接口Callable/WebAsyncTask释放 Tomcat 线程后,计数同步下降,贴合真实业务并发
Tomcat getActiveCount():Tomcat 工作线程活跃数
- 包含所有占用线程的请求,容易虚高
- 长连接、异步场景指标失真
二、方案 2:代码读取 Tomcat 排队等待请求堆积数
实现原理
通过ServletWebServerApplicationContext获取内嵌 Tomcat 连接器,强转为StandardThreadExecutor,直接读取线程池任务队列当前长度:
getQueueSize() = 线程池全部繁忙后,排队等待分配线程的请求数量。
Tomcat 队列监控 Controller
|
java
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardThreadExecutor;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@RestController
public class TomcatQueueMonitorController {
@Resource
private ServletWebServerApplicationContext webServerCtx;
@Resource
private RequestConcurrencyInterceptor concurrencyInterceptor;
/**
* 完整Tomcat监控指标接口
* 1. 业务真实并发(拦截器统计)
* 2. Tomcat线程池排队等待请求数
* 3. Tomcat活跃工作线程数
* 4. 最大线程上限、当前线程池总大小
*/
@GetMapping("/monitor/tomcat/stat")
public Map<String, Object> getTomcatFullStat() {
// 获取第一个端口的Tomcat连接器
Connector connector = webServerCtx.getWebServer().getConnectors()[0];
StandardThreadExecutor executor = (StandardThreadExecutor) connector.getProtocolHandler().getExecutor();
Map<String, Object> result = new HashMap<>(8);
// 1. 拦截器统计:仅Controller业务请求并发(推荐业务监控使用)
result.put("businessConcurrency", concurrencyInterceptor.getCurrentConcurrency());
// 2. Tomcat底层活跃工作线程(包含静态资源、长连接等)
result.put("tomcatActiveThread", executor.getActiveCount());
// 3. 队列中排队未分配线程的请求堆积数(核心预警指标)
result.put("waitQueueSize", executor.getQueueSize());
// 4. Tomcat配置最大工作线程
result.put("maxThreads", executor.getMaxThreads());
// 5. 当前线程池已创建总线程
result.put("currentPoolSize", executor.getPoolSize());
return result;
}
/**
* 单独获取排队堆积请求数(兼容旧逻辑)
*/
@GetMapping("/monitor/tomcat/queue")
public long getWaitRequestCount() {
Connector connector = webServerCtx.getWebServer().getConnectors()[0];
StandardThreadExecutor executor = (StandardThreadExecutor) connector.getProtocolHandler().getExecutor();
return executor.getQueueSize();
}
} |
三、关键指标释义与线上判断标准
访问接口 http://ip:port/monitor/tomcat/stat 得到 JSON,各字段含义:
|
字段 |
含义 |
线上风险判断 |
|
businessConcurrency |
拦截器统计,正在处理的业务接口并发 |
业务流量真实水位,压测、限流参考值 |
|
tomcatActiveThread |
Tomcat 活跃工作线程总数 |
包含静态资源、websocket,数值通常大于 businessConcurrency |
|
waitQueueSize |
排队等待线程的请求堆积数量 |
1. tomcatActiveThread == maxThreads 且 waitQueueSize 持续上涨 → 服务处理能力不足,大量请求排队超时
2. waitQueueSize 长期 > 50 需告警 |
|
maxThreads |
Tomcat 最大工作线程(默认 200) |
可通过server.tomcat.threads.max调大 |
|
currentPoolSize |
线程池已创建线程总数 |
冷启动缓慢扩容,流量平稳后稳定 |
四、使用限制与注意事项
- 容器限制
代码强依赖 Tomcat 内置类,若项目切换 Jetty/Undertow 内嵌容器,强转StandardThreadExecutor会抛类型转换异常,此时只能使用 Actuator 指标。
- 多端口服务兼容
getConnectors()[0]仅读取第一个监听端口;多端口服务需循环遍历数组,分别采集每个 Connector 的线程池指标。
- 异步接口指标差异
使用Callable、WebAsyncTask异步接口时,Tomcat 工作线程会立刻释放:
- businessConcurrency 仍会正常计数,更贴合业务真实并发场景
生产长期监控推荐方案
代码接口适合临时排查、本地压测;长期监控大盘建议开启 Actuator 导出标准化指标对接 Prometheus:
|
yaml
server:
tomcat:
mbeanregistry: enabled: true
management:
endpoints:
web:
exposure: include: metrics,prometheus |
核心指标:
- tomcat.threads.busy:Tomcat 活跃线程
- tomcat.threads.queue-size:排队队列长度
可结合自定义 Gauge 将拦截器businessConcurrency注册到 micrometer 统一监控。
五、线上调优建议
- 当waitQueueSize持续上涨、接口大量超时:
- 优先优化慢接口(数据库慢查询、第三方远程调用),减少单请求耗时
|
yaml
server:
tomcat:
threads:
max: 400
min-spare: 50
accept-count: 200 |
- 限流阈值参考:以businessConcurrency作为限流判断依据,不要依赖 Tomcat 原生活跃线程(存在虚高干扰)。
六、总结
- 拦截器计数优势:精准过滤非业务请求,异步场景数值不失真,最贴合业务层并发统计需求;
- Tomcat 底层队列读取优势:直接获取排队堆积请求数,快速判断流量打满、请求阻塞;
- 两套代码可共存,统一监控接口输出全维度指标,兼顾临时排查与线上容量评估场景。
所有评论(0)