你们公司的 QPS 是怎么统计出来的?这 5 种常见方法我踩过一半的坑!
三年前做电商秒杀项目,运维同学说 “网关 QPS 已经到 8000 了,赶紧扩容”,但我查应用监控却显示 “接口 QPS 才 3000”—— 两边数据差了一倍多,最后发现是网关统计时把 “健康检查请求” 也算进去了,白扩容了 3 台服务器。
作为 Java 的老开发,我太清楚 QPS 统计的重要性:它是判断系统承载能力、决定是否扩容的核心依据,统计不准会导致 “要么资源浪费,要么系统雪崩”。今天就从 业务场景、技术原理、核心代码、踩坑经验 四个维度,拆解 5 种常见的 QPS 统计方法,帮你避开我曾踩过的坑。
一、先明确:不同业务场景,QPS 统计的 “粒度” 不一样
在讲方法前,得先搞清楚 “你要统计什么粒度的 QPS”—— 不同场景关注的重点完全不同:
业务场景 | 统计粒度 | 核心需求 |
---|---|---|
电商秒杀 | 单个接口(如 /order/seckill) | 实时性(秒级更新)、准确性(排除无效请求) |
微服务集群监控 | 服务维度(如订单服务) | 全局视角(所有接口汇总)、低侵入 |
接口性能优化 | 方法级(如 createOrder 方法) | 细粒度(定位慢方法)、结合响应时间 |
离线容量评估 | 全天 / 峰值时段汇总 | 数据完整性(不丢日志)、可回溯 |
二、5 种 QPS 统计方法:从网关到应用,从实时到离线
每种方法都有自己的适用场景,我会结合 Java 项目常用技术栈(Spring Boot、Nginx、Prometheus 等),给出可直接复用的代码。
方法 1:网关层统计(全局视角,适合分布式项目)
适用场景:微服务集群,需要统计所有服务的总 QPS,或单个服务的入口 QPS(如 API 网关、Nginx)。原理:所有请求都经过网关,在网关层拦截请求,记录请求数和时间,按秒计算 QPS。
实战 1:Nginx 统计 QPS(中小项目首选)
Nginx 的access_log
会记录每一次请求,配合ngx_http_stub_status_module
模块,能快速统计 QPS。关注工众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册!
配置 Nginx(
nginx.conf
):
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;}}
}
查看实时 QPS: 访问
http://192.168.0.100:8080/nginx-status
,会显示:
Active connections: 200
server accepts handled requests10000 10000 80000
Reading: 0 Writing: 10 Waiting: 190
QPS 计算:
requests/时间
,比如 10 秒内请求 80000 次,QPS=8000。工具脚本:写个 Shell 脚本定时统计(每 1 秒执行一次):
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:Spring Cloud Gateway 统计 QPS(Java 微服务)
如果用 Spring Cloud Gateway,可通过自定义过滤器统计 QPS:
@Component
publicclass QpsStatisticsFilter implements GlobalFilter, Ordered {// 存储接口QPS:key=接口路径,value=原子计数器privatefinal Map<String, AtomicLong> pathQpsMap = new ConcurrentHashMap<>();// 定时1秒清零计数器(避免数值过大)@PostConstructpublic 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);}@Overridepublic 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);}@Overridepublic int getOrder() {return -1; // 过滤器优先级:数字越小越先执行}
}
踩坑经验:
网关统计会包含 “健康检查请求”(如 /actuator/health),需要过滤:在
filter
方法中加if (path.startsWith("/actuator")) return chain.filter(exchange);
。分布式网关(多节点)需汇总 QPS,可把数据推到 Prometheus,避免单节点统计不准。
方法 2:应用层埋点(细粒度,适合单服务接口统计)
适用场景:需要统计单个服务的接口级 QPS(如订单服务的 /create 接口),或方法级 QPS(如 Service 层的 createOrder 方法)。原理:用 AOP 或 Filter 拦截请求 / 方法,记录请求数,按秒计算 QPS(适合 Java 应用)。
实战:Spring AOP 统计接口 QPS
引入依赖(Spring Boot 项目):
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义切面(统计 Controller 接口 QPS):
@Aspect
@Component
@Slf4j
publicclass ApiQpsAspect {// 存储接口QPS:key=接口名(如com.example.OrderController.createOrder),value=计数器privatefinal Map<String, AtomicLong> apiQpsMap = new ConcurrentHashMap<>();// 定时1秒打印QPS并清零@PostConstructpublic 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();}
}
进阶优化:
过滤无效请求:在
countQps
中判断响应状态码,只统计 200/300 的有效请求;结合响应时间:在环绕通知中记录方法执行时间,同时统计 “QPS + 平均响应时间”:
// 记录响应时间
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);
踩坑经验:
并发安全:必须用
AtomicLong
计数,避免long
变量的线程安全问题;性能影响:AOP 会增加微小开销(单请求约 0.1ms),生产环境可通过
@Conditional
控制只在非生产环境启用,或用 Java Agent 替代 AOP 减少侵入。
方法 3:监控工具统计(实时可视化,适合运维监控)
适用场景:需要实时可视化 QPS、历史趋势分析、告警(如 QPS 超过阈值自动发告警),主流方案是Prometheus + Grafana
。原理:应用埋点暴露指标(如 QPS、响应时间),Prometheus 定时拉取指标,Grafana 展示图表。
实战:Spring Boot + Prometheus + Grafana 统计 QPS
引入依赖:
<!-- Micrometer:对接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>
配置 Prometheus(
application.yml
):
spring:application:name:order-service# 服务名,用于Prometheus识别management:
endpoints:web:exposure:include:prometheus# 暴露/prometheus端点
metrics:tags:application:${spring.application.name}# 给指标加服务名标签distribution:percentiles-histogram:http:server:requests:true# 开启响应时间分位数统计
埋点统计 QPS(用 Micrometer 的
MeterRegistry
):
@RestController
@RequestMapping("/order")
publicclass OrderController {// 注入MeterRegistryprivatefinal MeterRegistry meterRegistry;@Autowiredpublic OrderController(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;}@PostMapping("/create")public String createOrder() {// 统计/create接口的QPS:meterRegistry会自动按秒聚合Counter.builder("order.create.qps") // 指标名.description("订单创建接口QPS") // 描述.register(meterRegistry).increment(); // 计数器自增// 业务逻辑return"success";}
}
配置 Prometheus 拉取指标(
prometheus.yml
):
scrape_configs:- job_name: 'order-service'scrape_interval: 1s # 每秒拉取一次(实时性高)static_configs:- targets: ['192.168.0.101:8080'] # 应用地址(暴露的actuator端口)
Grafana 配置图表:
导入 Prometheus 数据源,写 QPS 查询语句:
sum(rate(order_create_qps_total[1m])) by (application)
(1 分钟内的平均 QPS);配置告警:当 QPS>5000 时,发送邮件 / 钉钉告警。
踩坑经验:
拉取间隔:
scrape_interval
不要设太小(如 < 100ms),会增加应用和 Prometheus 的压力;指标命名:按 “业务 + 接口 + 指标类型” 命名(如
order_create_qps
),避免和其他指标冲突。
企业级实战总结40讲
推荐一下陈某新出的小册子总结了企业中后端的各种核心问题解决方案,包括JVM、数据库、性能调优等企业级落地40个痛点问题以及解决方案....
原价99,今日优惠价格11.9永久买断!目前已经全部更新完,大家可以扫描下方二维码在线订阅!
文章目录可以扫码进入查看
方法 4:日志分析统计(离线,适合容量评估)
适用场景:需要离线统计 QPS(如分析昨天秒杀的峰值 QPS),或排查历史问题(如上周三 QPS 突增的原因)。原理:应用打印请求日志(包含时间、接口、状态码),用 ELK(Elasticsearch+Logstash+Kibana)或 Flink 分析日志,计算 QPS。
实战:ELK 统计离线 QPS
应用打印结构化日志(用 Logback):
<!-- logback-spring.xml -->
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>/var/log/order-service/request.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>/var/log/order-service/request.%d{yyyy-MM-dd}.log</fileNamePattern></rollingPolicy><!-- 输出JSON格式日志 --><encoder class="net.logstash.logback.encoder.LogstashEncoder"><includeMdcKeyName>requestPath</includeMdcKeyName><includeMdcKeyName>requestTime</includeMdcKeyName></encoder>
</appender><root level="INFO"><appender-ref ref="JSON_FILE" />
</root>
MDC 埋点记录请求信息:
@Component
publicclass RequestLogFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {try {// 记录请求路径到MDCMDC.put("requestPath", request.getRequestURI());// 记录请求时间MDC.put("requestTime", String.valueOf(System.currentTimeMillis()));chain.doFilter(request, response);} finally {// 清除MDC,避免线程复用导致数据污染MDC.clear();}}
}
Logstash 收集日志到 Elasticsearch(
logstash.conf
):
input {file {path => "/var/log/order-service/request.*.log" # 日志路径start_position => "beginning"sincedb_path => "/dev/null" # 每次重启都重新读取所有日志}
}filter {json {source => "message" # 解析JSON格式日志}# 提取时间字段(转为Elasticsearch时间格式)date {match => ["requestTime", "yyyy-MM-dd HH:mm:ss"]target => "@timestamp"}
}output {elasticsearch {hosts => ["192.168.0.102:9200"] # Elasticsearch地址index => "order-request-%{+YYYY.MM.dd}" # 索引名}
}
Kibana 分析 QPS:
进入 Kibana 的 “Discover”,选择
order-request-*
索引;用 “Visualize” 创建柱状图,X 轴选 “时间(1 秒间隔)”,Y 轴选 “文档数”(即每秒请求数),就是 QPS 趋势图。
踩坑经验:
日志切割:按天切割日志,避免单个日志文件太大(如超过 10GB),导致 Logstash 读取缓慢;
字段清洗:过滤掉无用日志(如 DEBUG 级别的日志),减少 Elasticsearch 的存储压力。
方法 5:数据库层辅助统计(间接,适合排查 DB 瓶颈)
适用场景:当 QPS 突增导致数据库压力大时,通过数据库指标间接判断 QPS(如 MySQL 的连接数、慢查询数)。原理:数据库的请求数和应用 QPS 正相关(如 1 个订单请求对应 2 次 DB 查询),可通过 DB 指标反推应用 QPS。
实战:MySQL 统计连接数和慢查询
查看 MySQL 实时连接数:
-- 查看当前连接数(QPS高时连接数会增长)
show status like 'Threads_connected';
-- 查看每秒查询数(DB层QPS,可反推应用QPS)
show status like 'Queries';
配置慢查询日志(
my.cnf
):
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1 # 超过1秒的查询记录为慢查询
分析慢查询与 QPS 的关系: 当应用 QPS 突增时,慢查询数会同步增长(如秒杀时 QPS 从 1000 涨到 5000,慢查询从 10 / 秒涨到 100 / 秒),可通过慢查询日志定位瓶颈 SQL。
踩坑经验:
间接统计有误差:DB QPS ≠ 应用 QPS(1 个应用请求可能对应多个 DB 查询),只能作为辅助判断;
避免频繁执行
show status
:该命令会占用 DB 资源,建议每 10 秒执行一次,而非实时执行。
三、八年经验总结:QPS 统计的选型指南和避坑清单
1. 选型指南(按场景选方法)
需求场景 | 推荐方法 | 优点 | 缺点 |
---|---|---|---|
实时全局 QPS 监控 | 网关层(Nginx/Gateway)+ Prometheus | 全局视角、实时性高 | 配置复杂(多节点需汇总) |
单服务接口级 QPS 统计 | 应用层 AOP + Micrometer | 细粒度、侵入性低 | 分布式场景需汇总数据 |
离线容量评估 | ELK 日志分析 | 可回溯、数据完整 | 实时性差(延迟分钟级) |
排查 DB 瓶颈 | 数据库层辅助统计 | 无需应用埋点 | 误差大,只能间接判断 |
2. 避坑清单(我踩过的坑,你别再踩)
别统计无效请求:过滤健康检查(/actuator/health)、爬虫请求(User-Agent 包含 spider),避免 QPS 虚高;
并发安全要保证:计数用
AtomicLong
或Counter
(Micrometer),避免用long
变量导致计数不准;实时性和性能平衡:Prometheus 拉取间隔设为 1-5 秒,AOP 埋点只统计核心接口,避免过度消耗资源;
多节点数据汇总:分布式网关或多实例应用,需将 QPS 数据推到统一监控平台(如 Prometheus),避免单节点统计偏差;
结合业务上下文:QPS 统计要关联业务场景(如秒杀时的 QPS 和日常 QPS 标准不同),避免 “唯 QPS 论”。
最后:QPS 统计的本质是 “为决策服务”
八年开发下来,我发现很多人陷入 “追求精确 QPS” 的误区 —— 其实 QPS 统计的核心目的是 “判断系统是否能扛住流量,是否需要扩容”,而非追求 “精确到个位数的 QPS”。
比如秒杀场景,只要统计出 QPS 超过 4000(系统阈值),就该扩容,至于是 4001 还是 4002,差别不大。关键是选对统计方法,避开无效请求、并发安全、数据偏差这些坑,让 QPS 数据能真正指导决策。
下次有人问你 “你们公司 QPS 怎么统计的”,别只说 “用了 Prometheus”,把场景、方法、踩过的坑讲清楚 —— 这才是八年开发该有的深度。