你們公司的 QPS 是怎么統計出來的?這五種常見方法我踩過一半的坑!
三年前做電商秒殺項目,運維同學說 “網關 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 requests
10000 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秒清零計數器(避免數值過大)
@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; // 過濾器優先級:數字越小越先執行
}
}踩坑經驗:
- 網關統計會包含 “健康檢查請求”(如 /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并清零
@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();
}
}進階優化:
- 過濾無效請求:在
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 {
// 注入MeterRegistry
privatefinal MeterRegistry meterRegistry;
@Autowired
public 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),避免和其他指標沖突。
































