一、为什么QPS统计如此重要
1. 性能监控的核心指标
- QPS(Queries Per Second):每秒查询率,是衡量系统性能的关键指标
- 容量规划:帮助预估系统承载能力和扩展需求
- 故障预警:及时发现系统性能瓶颈和异常
2. 不同场景下的QPS统计策略
- 网关层统计:适用于微服务架构的整体监控
- 应用层统计:适用于具体业务接口的精细化监控
- 基础设施统计:基于Nginx等基础组件的统计方案
二、Nginx网关层QPS统计实战
1. 基础配置实现
1.1 nginx.conf配置详解
ini
http {
# 开启状态监控页面
server {
listen 8080;
location /nginx-status {
stub_status on;
allow 192.168.0.0/24; # 只允许内网访问
deny all;
}
}
# 记录详细请求日志(用于离线分析)
log_format main '$remote_addr [$time_local] "$request" $status $request_time';
server {
listen 80;
server_name api.example.com;
access_log /var/log/nginx/api-access.log main; # 日志路径
# 转发到后端服务
location / {
proxy_pass http://backend-service;
}
}
}
配置要点说明:
stub_status on:启用Nginx状态模块- 访问控制:只允许内网IP访问状态页面,保证安全性
- 自定义日志格式:便于后续分析
1.2 实时QPS查看
访问 http://192.168.0.100:8080/nginx-status,返回信息:
yaml
Active connections: 200
server accepts handled requests
10000 10000 100000
Reading: 0 Writing: 1 Waiting: 190
数据解读:
Active connections:当前活跃连接数accepts/handled/requests:分别表示接受的连接数、处理的连接数、总请求数- QPS计算公式:requests/时间间隔 100000/10 =10000 QPS
2. 自动化QPS统计脚本
2.1 Shell脚本实现
bash
while true; do
# 取当前请求数
current=$(curl -s http://192.168.0.100:8080/nginx-status | awk 'NR==3 {print $3}')
sleep 1
# 取1秒后请求数
next=$(curl -s http://192.168.0.100:8080/nginx-status | awk 'NR==3 {print $3}')
qps=$((next - current))
echo "当前QPS: $qps"
done
2.2 脚本优化建议
- 添加错误处理机制
- 输出格式化为JSON便于集成
- 支持阈值告警功能
三、Spring Cloud Gateway QPS统计实践
1. 自定义全局过滤器实现
1.1 QpsStatisticsFilter核心代码
java
@Component
public class QpsStatisticsFilter implements GlobalFilter, Ordered {
// 存储接口QPS:key=接口路径,value=原子计数器
private final Map<String, AtomicLong> pathQpsMap = new ConcurrentHashMap<>();
// 定时1秒清零计数器(避免数值过大)
@PostConstruct
public void init() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> {
// 遍历所有接口,打印QPS后清零
pathQpsMap.forEach((path, counter) -> {
long qps = counter.getAndSet(0);
log.info("接口[{}] QPS: {}", path, qps);
});
}, 0, 1, TimeUnit.SECONDS);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求路径(如/order/seckill)
String path = exchange.getRequest().getPath().value();
// 计数器自增(线程安全)
pathQpsMap.computeIfAbsent(path, k -> new AtomicLong()).incrementAndGet();
// 继续转发请求
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1; // 过滤器优先级:数字越小越先执行
}
}
1.2 关键设计思路
ConcurrentHashMap:保证线程安全的同时提供高并发性能AtomicLong:原子操作确保计数准确性ScheduledExecutorService:定时任务清理计数器
2. 踩坑经验总结
2.1 健康检查请求过滤
java
if (path.startsWith("/actuator")) return chain.filter(exchange);
四、应用层AOP QPS统计深度解析
1. Spring AOP实现方案
1.1 依赖配置
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1.2 自定义切面实现
java
@Aspect
@Component
@Slf4j
public class ApiQpsAspect {
// 存储接口QPS:key=接口名(如com.example.OrderController.createOrder),value=计数器
private final Map<String, AtomicLong> apiQpsMap = new ConcurrentHashMap<>();
// 定时1秒打印QPS并清零
@PostConstruct
public void scheduleQpsPrint() {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
apiQpsMap.forEach((api, counter) -> {
long qps = counter.getAndSet(0);
if (qps > 0) { // 只打印有请求的接口
log.info("[QPS统计] 接口: {}, QPS: {}", api, qps);
}
});
}, 0, 1, TimeUnit.SECONDS);
}
// 切入点:拦截所有Controller方法
@Pointcut("execution(* com.example.*.controller..*(..))")
public void apiPointcut() {}
// 环绕通知:统计请求数
@Around("apiPointcut()")
public Object countQps(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取接口名(类名+方法名)
String apiName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
// 计数器自增
apiQpsMap.computeIfAbsent(apiName, k -> new AtomicLong()).incrementAndGet();
// 执行原方法
return joinPoint.proceed();
}
}
2. 进阶优化方案
2.1 有效请求过滤
java
// 在countQps方法中添加响应状态判断
if (response.getStatusCode().is2xxSuccessful()) {
apiQpsMap.computeIfAbsent(apiName, k -> new AtomicLong()).incrementAndGet();
}
2.2 响应时间统计增强
java
// 记录响应时间
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
// 存储响应时间(key=接口名,value=时间列表)
timeMap.computeIfAbsent(apiName, k -> new CopyOnWriteArrayList<>()).add(cost);
// 计算平均响应时间
double avgTime = timeMap.get(apiName).stream().mapToLong(Long::longValue).average().orElse(0);
3. 生产环境最佳实践
3.1 并发安全保障
- 必须使用
AtomicLong进行计数 - 避免
long变量的线程安全问题
3.2 性能影响控制
- AOP开销:单请求约0.1ms,影响较小
- 条件启用 :使用
@Conditional控制只在非生产环境启用 - Java Agent替代:减少代码侵入性
五、三种方案对比与选型建议
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Nginx统计 | 简单易用,性能好 | 无法区分业务接口 | 中小项目,基础监控 |
| Gateway统计 | 统一入口,功能丰富 | 部署复杂,学习成本高 | 微服务架构 |
| AOP统计 | 精细控制,业务感知强 | 侵入性强,性能开销 | 单体应用,业务监控 |
选型建议:
- 中小项目:优先选择Nginx方案
- 微服务架构:推荐Gateway方案
- 精细化监控:采用AOP方案
- 混合使用:多维度监控,相互验证
通过本文的深入分析,相信你已经掌握了不同场景下QPS统计的最佳实践。记住,选择合适的方案比盲目追求复杂的技术更重要!