SpringBoot實(shí)現(xiàn)QPS監(jiān)控的完整代碼
一、什么是QPS?—— 系統(tǒng)的"心跳頻率"
想象一下你的系統(tǒng)就像一個(gè)忙碌的外賣小哥,QPS(Query Per Second)就是他每秒能送多少份外賣!如果小哥每秒只能送1單,那估計(jì)顧客早就餓暈在廁所了;要是每秒能送100單,那他絕對(duì)是"閃電俠"附體!
正常系統(tǒng)的QPS就像人的心跳:
- 60-100 QPS:健康小伙子,心跳平穩(wěn)
- 100-1000 QPS:健身達(dá)人,有點(diǎn)小激動(dòng)
- 1000+ QPS:跑馬拉松呢!快喘口氣!
- 10000+ QPS:這貨是打了雞血吧?
二、方案大比拼——給系統(tǒng)裝上"智能手環(huán)"
方案1:簡易版手環(huán)(AOP攔截器)
適合小項(xiàng)目,就像給系統(tǒng)戴個(gè)手環(huán)
@Slf4j
@Aspect
@Component
public class QpsMonitorAspect {
// 用ConcurrentHashMap存計(jì)數(shù)器,線程安全!
private final ConcurrentHashMap<String, AtomicLong> counterMap = new ConcurrentHashMap<>();
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@PostConstruct
public void init() {
log.info("QPS監(jiān)控龜龜已啟動(dòng),開始慢慢爬...");
// 每秒統(tǒng)計(jì)一次,像烏龜一樣穩(wěn)定
scheduler.scheduleAtFixedRate(this::printQps, 0, 1, TimeUnit.SECONDS);
}
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping)")
public Object countQps(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
// 計(jì)數(shù)器自增,像小松鼠囤松果一樣積極
counterMap.computeIfAbsent(methodName, k -> new AtomicLong(0))
.incrementAndGet();
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
// 順便記錄一下響應(yīng)時(shí)間,看看系統(tǒng)是不是"老了腿腳慢"
if (cost > 1000) {
log.warn("方法 {} 執(zhí)行了 {}ms,比蝸牛還慢!", methodName, cost);
}
}
}
private void printQps() {
if (counterMap.isEmpty()) {
log.info("系統(tǒng)在睡大覺,沒有請(qǐng)求...");
return;
}
StringBuilder sb = new StringBuilder("\n========== QPS報(bào)告 ==========\n");
counterMap.forEach((method, counter) -> {
long qps = counter.getAndSet(0); // 重置計(jì)數(shù)器
String status = "";
if (qps > 1000) status = "";
if (qps > 5000) status = "";
sb.append(String.format("%s %-40s : %d QPS%n",
status, method, qps));
});
sb.append("================================");
log.info(sb.toString());
}
}
方案2:專業(yè)版體檢儀(Micrometer + Prometheus)
適合大項(xiàng)目,就像給系統(tǒng)做全面體檢
@Configuration
public class MetricsConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> {
registry.config().commonTags("application", "my-awesome-app");
log.info("系統(tǒng)體檢中心開業(yè)啦!歡迎隨時(shí)來檢查身體~");
};
}
}
@Service
public class OrderService {
private final Counter orderCounter;
private final Timer orderTimer;
private final MeterRegistry meterRegistry;
public OrderService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 創(chuàng)建訂單計(jì)數(shù)器,像收銀機(jī)一樣"叮叮叮"
this.orderCounter = Counter.builder("order.count")
.description("訂單數(shù)量統(tǒng)計(jì)")
.tag("type", "create")
.register(meterRegistry);
// 創(chuàng)建訂單耗時(shí)計(jì)時(shí)器
this.orderTimer = Timer.builder("order.process.time")
.description("訂單處理時(shí)間")
.register(meterRegistry);
}
public Order createOrder(OrderDTO dto) {
// 記錄方法執(zhí)行時(shí)間
return orderTimer.record(() -> {
log.debug("正在打包訂單,請(qǐng)稍候...");
// 業(yè)務(wù)邏輯...
Order order = doCreateOrder(dto);
// 訂單創(chuàng)建成功,計(jì)數(shù)器+1
orderCounter.increment();
// 動(dòng)態(tài)QPS統(tǒng)計(jì)(最近1分鐘)
double qps = meterRegistry.get("order.count")
.counter()
.measure()
.stream()
.findFirst()
.map(Measurement::getValue)
.orElse(0.0) / 60.0;
if (qps > 100) {
log.warn("訂單處理太快了!當(dāng)前QPS: {}/s,考慮加點(diǎn)運(yùn)費(fèi)?", qps);
}
return order;
});
}
// 動(dòng)態(tài)查看QPS的API
@GetMapping("/metrics/qps")
public Map<String, Object> getRealTimeQps() {
Map<String, Object> metrics = new HashMap<>();
// 收集所有接口的QPS
meterRegistry.getMeters().forEach(meter -> {
String meterName = meter.getId().getName();
if (meterName.contains(".count")) {
double qps = meter.counter().count() / 60.0; // 轉(zhuǎn)換為每秒
metrics.put(meterName, String.format("%.2f QPS", qps));
// 添加表情包增強(qiáng)可視化效果
String emoji = "";
if (qps > 100) emoji = "";
if (qps > 500) emoji = "";
metrics.put(meterName + "_emoji", emoji);
}
});
metrics.put("report_time", LocalDateTime.now());
metrics.put("message", "系統(tǒng)當(dāng)前狀態(tài)良好,吃嘛嘛香!");
return metrics;
}
}
方案3:豪華版監(jiān)控大屏(Spring Boot Admin)
給老板看的,必須高大上!
# application.yml
spring:
boot:
admin:
client:
url: http://localhost:9090 # Admin Server地址
instance:
name: "青龍系統(tǒng)"
metadata:
owner: "碼農(nóng)小張"
department: "爆肝事業(yè)部"
management:
endpoints:
web:
exposure:
include: "*" # 暴露所有端點(diǎn),不穿"隱身衣"
metrics:
export:
prometheus:
enabled: true
endpoint:
health:
show-details: ALWAYS
@RestController
@Slf4j
public class QpsDashboardController {
@GetMapping("/dashboard/qps")
public String qpsDashboard() {
// 模擬從各個(gè)服務(wù)收集QPS數(shù)據(jù)
Map<String, Double> serviceQps = getClusterQps();
// 生成ASCII藝術(shù)報(bào)表
StringBuilder dashboard = new StringBuilder();
dashboard.append("\n");
dashboard.append("╔══════════════════════════════════════════╗\n");
dashboard.append("║ 系統(tǒng)QPS監(jiān)控大屏 ║\n");
dashboard.append("╠══════════════════════════════════════════╣\n");
serviceQps.forEach((service, qps) -> {
// 生成進(jìn)度條
int bars = (int) Math.min(qps / 10, 50);
String progressBar = "█".repeat(bars) +
"?".repeat(50 - bars);
String status = "正常";
if (qps > 500) status = "警告";
if (qps > 1000) status = "緊急";
dashboard.append(String.format("║ %-15s : %-30s ║\n",
service, progressBar));
dashboard.append(String.format("║ %6.1f QPS %-20s ║\n",
qps, status));
});
dashboard.append("╚══════════════════════════════════════════╝\n");
// 添加系統(tǒng)健康建議
dashboard.append("\n 系統(tǒng)建議:\n");
double maxQps = serviceQps.values().stream().max(Double::compare).orElse(0.0);
if (maxQps < 50) {
dashboard.append(" 系統(tǒng)有點(diǎn)閑,可以考慮接點(diǎn)私活~ \n");
} else if (maxQps > 1000) {
dashboard.append(" 系統(tǒng)快冒煙了!快加機(jī)器!\n");
} else {
dashboard.append(" 狀態(tài)完美,繼續(xù)保持!\n");
}
return dashboard.toString();
}
// 定時(shí)推送QPS警告
@Scheduled(fixedRate = 60000)
public void checkQpsAlert() {
Map<String, Double> currentQps = getClusterQps();
currentQps.forEach((service, qps) -> {
if (qps > 1000) {
log.error("救命!{}服務(wù)QPS爆表了:{},快看看是不是被爬了!",
service, qps);
// 這里可以接入釘釘/企業(yè)微信告警
sendAlertToDingTalk(service, qps);
}
});
}
private void sendAlertToDingTalk(String service, double qps) {
String message = String.format(
"{\"msgtype\": \"text\", \"text\": {\"content\": \"%s服務(wù)QPS異常:%.1f,快去看看吧!\"}}",
service, qps
);
// 調(diào)用釘釘webhook
log.warn("已發(fā)送釘釘告警:{}", message);
}
}
三、方案詳細(xì)實(shí)施步驟
方案1實(shí)施步驟(簡易版):
- 添加依賴:給你的
pom.xml來點(diǎn)"維生素"
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 啟用AOP:在主類上貼個(gè)"創(chuàng)可貼"
@SpringBootApplication
@EnableAspectJAutoProxy // 啟用AOP魔法
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
System.out.println("QPS監(jiān)控小雞破殼而出!");
}
}
- 創(chuàng)建切面類:如上文的
QpsMonitorAspect - 測(cè)試一下:瘋狂刷新接口,看看控制臺(tái)輸出
方案2實(shí)施步驟(專業(yè)版):
- 添加全家桶依賴:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 配置application.yml:
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
endpoint:
metrics:
enabled: true
- 訪問監(jiān)控?cái)?shù)據(jù):
http://localhost:8080/actuator/metrics # 查看所有指標(biāo) http://localhost:8080/actuator/prometheus # Prometheus格式
方案3實(shí)施步驟(豪華版):
- 搭建Spring Boot Admin Server:
@SpringBootApplication
@EnableAdminServer
public class AdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServerApplication.class, args);
System.out.println("監(jiān)控大屏已就位,陛下請(qǐng)檢閱!");
}
}
- 客戶端配置:如上文yml配置
- 訪問Admin UI:
http://localhost:9090
四、QPS統(tǒng)計(jì)的進(jìn)階技巧
1. 滑動(dòng)窗口統(tǒng)計(jì)(最近N秒的QPS)
public class SlidingWindowQpsCounter {
// 用環(huán)形隊(duì)列實(shí)現(xiàn)滑動(dòng)窗口
private final LinkedList<Long> timestamps = new LinkedList<>();
private final int windowSeconds;
public SlidingWindowQpsCounter(int windowSeconds) {
this.windowSeconds = windowSeconds;
log.info("創(chuàng)建滑動(dòng)窗口監(jiān)控,窗口大?。簕}秒", windowSeconds);
}
public synchronized void hit() {
long now = System.currentTimeMillis();
timestamps.add(now);
// 移除窗口外的記錄
while (!timestamps.isEmpty() &&
now - timestamps.getFirst() > windowSeconds * 1000) {
timestamps.removeFirst();
}
}
public double getQps() {
return timestamps.size() / (double) windowSeconds;
}
}
2. 分位數(shù)統(tǒng)計(jì)(P90/P95/P99響應(yīng)時(shí)間)
@Bean
public MeterRegistryCustomizer<MeterRegistry> addQuantiles() {
return registry -> {
DistributionStatisticConfig config = DistributionStatisticConfig.builder()
.percentiles(0.5, 0.9, 0.95, 0.99) // 50%, 90%, 95%, 99%
.percentilePrecision(2)
.build();
registry.config().meterFilter(
new MeterFilter() {
@Override
public DistributionStatisticConfig configure(
Meter.Id id, DistributionStatisticConfig config) {
if (id.getName().contains(".timer")) {
return config.merge(DistributionStatisticConfig.builder()
.percentiles(0.5, 0.9, 0.95, 0.99)
.build());
}
return config;
}
}
);
log.info("分位數(shù)統(tǒng)計(jì)已啟用,準(zhǔn)備精準(zhǔn)打擊慢查詢!");
};
}
3. 基于QPS的自動(dòng)熔斷
@Component
public class AdaptiveCircuitBreaker {
private volatile boolean circuitOpen = false;
private double currentQps = 0;
@Scheduled(fixedRate = 1000)
public void monitorAndAdjust() {
// 獲取當(dāng)前QPS
currentQps = calculateCurrentQps();
if (circuitOpen && currentQps < 100) {
circuitOpen = false;
log.info("熔斷器關(guān)閉,系統(tǒng)恢復(fù)供電!當(dāng)前QPS: {}", currentQps);
} else if (!circuitOpen && currentQps > 1000) {
circuitOpen = true;
log.error("熔斷器觸發(fā)!QPS過高: {},系統(tǒng)進(jìn)入保護(hù)模式", currentQps);
}
// 動(dòng)態(tài)調(diào)整線程池大小
adjustThreadPool(currentQps);
}
private void adjustThreadPool(double qps) {
int suggestedSize = (int) (qps * 0.5); // 經(jīng)驗(yàn)公式
log.debug("建議線程池大小調(diào)整為: {} (基于QPS: {})", suggestedSize, qps);
}
}
五、總結(jié):給系統(tǒng)做QPS監(jiān)控就像...
1.為什么要監(jiān)控QPS?
- 對(duì)系統(tǒng):就像給汽車裝時(shí)速表,超速了會(huì)報(bào)警
- 對(duì)開發(fā):就像給程序員裝"健康手環(huán)",代碼跑太快會(huì)冒煙
- 對(duì)老板:就像給公司裝"業(yè)績大屏",數(shù)字好看心情好
2.各方案選擇建議:
- 初創(chuàng)公司/小項(xiàng)目:用方案1,簡單粗暴見效快,就像"創(chuàng)可貼"
- 中型項(xiàng)目/微服務(wù):用方案2,全面體檢不遺漏,就像"年度體檢"
- 大型分布式系統(tǒng):用方案3,全景監(jiān)控?zé)o死角,就像"衛(wèi)星監(jiān)控"
3.最佳實(shí)踐提醒:
// 記住這些黃金法則:
public class QpsGoldenRules {
// 法則1:監(jiān)控不是為了監(jiān)控而監(jiān)控
public static final String RULE_1 = "別讓監(jiān)控把系統(tǒng)壓垮了!";
// 法則2:告警要有意義
public static final String RULE_2 = "狼來了喊多了,就沒人信了!";
// 法則3:數(shù)據(jù)要可視化
public static final String RULE_3 = "老板看不懂的圖表都是廢紙!";
// 法則4:要有應(yīng)對(duì)方案
public static final String RULE_4 = "光報(bào)警不解決,要你有何用?";
}
4.最后總結(jié):
給你的系統(tǒng)加QPS監(jiān)控,就像是:
- 給外賣小哥配了計(jì)步器 —— 知道他每天跑多少
- 給程序員裝了鍵盤計(jì)數(shù)器 —— 知道他有多卷
- 給系統(tǒng)裝了"心電圖機(jī)" —— 隨時(shí)掌握生命體征
記住,一個(gè)健康的系統(tǒng)應(yīng)該:
- 平時(shí) 心跳平穩(wěn)(QPS穩(wěn)定)
- 大促時(shí) 適當(dāng)興奮(彈性擴(kuò)容)
- 故障時(shí) 自動(dòng)降壓(熔斷降級(jí))
現(xiàn)在就去給你的SpringBoot系統(tǒng)裝上"智能手環(huán)"吧!讓它在代碼的海洋里,游得更快、更穩(wěn)、更健康!
最后的最后:監(jiān)控千萬條,穩(wěn)定第一條;QPS不規(guī)范,運(yùn)維兩行淚!
以上就是SpringBoot實(shí)現(xiàn)QPS監(jiān)控的完整代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot實(shí)現(xiàn)QPS監(jiān)控的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)Word轉(zhuǎn)PDF的全過程
在IT領(lǐng)域,文檔格式轉(zhuǎn)換是常見的任務(wù)之一,特別是在管理大量文本數(shù)據(jù)時(shí),本文將詳細(xì)探討如何利用Java技術(shù)將Word文檔(.docx)轉(zhuǎn)換成PDF格式,需要的朋友可以參考下2025-04-04
SpringBoot自定義MessageConvert詳細(xì)講解
正在學(xué)習(xí)SpringBoot,在自定義MessageConverter時(shí)發(fā)現(xiàn):為同一個(gè)返回值類型配置多個(gè)MessageConverter時(shí),可能會(huì)發(fā)生響應(yīng)數(shù)據(jù)格式錯(cuò)誤,或406異常(客戶端無法接收相應(yīng)數(shù)據(jù))。在此記錄一下解決問題以及追蹤源碼的過程2023-01-01
SpringBoot利用模板實(shí)現(xiàn)自動(dòng)生成Word合同的功能
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何利用模板實(shí)現(xiàn)自動(dòng)生成Word合同的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2025-12-12
Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟詳解
這篇文章主要介紹了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
SpringBoot聲明式事務(wù)的簡單運(yùn)用說明
這篇文章主要介紹了SpringBoot聲明式事務(wù)的簡單運(yùn)用說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Maven里面沒有plugins dependence問題解決
在整合Nacos和Dubbo時(shí),出現(xiàn)Maven錯(cuò)誤可以通過檢查父模塊的依賴解決,問題源于MySQL驅(qū)動(dòng)版本不兼容,移除特定依賴并刷新pom文件可恢復(fù)項(xiàng)目,執(zhí)行clean命令,查看報(bào)錯(cuò),感興趣的可以了解一下2024-10-10

