一、什么是qps?—— 系统的"心跳频率"
想象一下你的系统就像一个忙碌的外卖小哥,qps(query per second)就是他每秒能送多少份外卖!如果小哥每秒只能送1单,那估计顾客早就饿晕在厕所了;要是每秒能送100单,那他绝对是"闪电侠"附体!
正常系统的qps就像人的心跳:
- 60-100 qps:健康小伙子,心跳平稳
- 100-1000 qps:健身达人,有点小激动
- 1000+ qps:跑马拉松呢!快喘口气!
- 10000+ qps:这货是打了鸡血吧?
二、方案大比拼——给系统装上"智能手环"
方案1:简易版手环(aop拦截器)
适合小项目,就像给系统戴个手环
@slf4j
@aspect
@component
public class qpsmonitoraspect {
// 用concurrenthashmap存计数器,线程安全!
private final concurrenthashmap<string, atomiclong> countermap = new concurrenthashmap<>();
private scheduledexecutorservice scheduler = executors.newscheduledthreadpool(1);
@postconstruct
public void init() {
log.info("qps监控龟龟已启动,开始慢慢爬...");
// 每秒统计一次,像乌龟一样稳定
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();
// 计数器自增,像小松鼠囤松果一样积极
countermap.computeifabsent(methodname, k -> new atomiclong(0))
.incrementandget();
long start = system.currenttimemillis();
try {
return joinpoint.proceed();
} finally {
long cost = system.currenttimemillis() - start;
// 顺便记录一下响应时间,看看系统是不是"老了腿脚慢"
if (cost > 1000) {
log.warn("方法 {} 执行了 {}ms,比蜗牛还慢!", methodname, cost);
}
}
}
private void printqps() {
if (countermap.isempty()) {
log.info("系统在睡大觉,没有请求...");
return;
}
stringbuilder sb = new stringbuilder("\n========== qps报告 ==========\n");
countermap.foreach((method, counter) -> {
long qps = counter.getandset(0); // 重置计数器
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:专业版体检仪(micrometer + prometheus)
适合大项目,就像给系统做全面体检
@configuration
public class metricsconfig {
@bean
public meterregistrycustomizer<meterregistry> metricscommontags() {
return registry -> {
registry.config().commontags("application", "my-awesome-app");
log.info("系统体检中心开业啦!欢迎随时来检查身体~");
};
}
}
@service
public class orderservice {
private final counter ordercounter;
private final timer ordertimer;
private final meterregistry meterregistry;
public orderservice(meterregistry meterregistry) {
this.meterregistry = meterregistry;
// 创建订单计数器,像收银机一样"叮叮叮"
this.ordercounter = counter.builder("order.count")
.description("订单数量统计")
.tag("type", "create")
.register(meterregistry);
// 创建订单耗时计时器
this.ordertimer = timer.builder("order.process.time")
.description("订单处理时间")
.register(meterregistry);
}
public order createorder(orderdto dto) {
// 记录方法执行时间
return ordertimer.record(() -> {
log.debug("正在打包订单,请稍候...");
// 业务逻辑...
order order = docreateorder(dto);
// 订单创建成功,计数器+1
ordercounter.increment();
// 动态qps统计(最近1分钟)
double qps = meterregistry.get("order.count")
.counter()
.measure()
.stream()
.findfirst()
.map(measurement::getvalue)
.orelse(0.0) / 60.0;
if (qps > 100) {
log.warn("订单处理太快了!当前qps: {}/s,考虑加点运费?", qps);
}
return order;
});
}
// 动态查看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; // 转换为每秒
metrics.put(metername, string.format("%.2f qps", qps));
// 添加表情包增强可视化效果
string emoji = "";
if (qps > 100) emoji = "";
if (qps > 500) emoji = "";
metrics.put(metername + "_emoji", emoji);
}
});
metrics.put("report_time", localdatetime.now());
metrics.put("message", "系统当前状态良好,吃嘛嘛香!");
return metrics;
}
}
方案3:豪华版监控大屏(spring boot admin)
给老板看的,必须高大上!
# application.yml
spring:
boot:
admin:
client:
url: http://localhost:9090 # admin server地址
instance:
name: "青龙系统"
metadata:
owner: "码农小张"
department: "爆肝事业部"
management:
endpoints:
web:
exposure:
include: "*" # 暴露所有端点,不穿"隐身衣"
metrics:
export:
prometheus:
enabled: true
endpoint:
health:
show-details: always
@restcontroller
@slf4j
public class qpsdashboardcontroller {
@getmapping("/dashboard/qps")
public string qpsdashboard() {
// 模拟从各个服务收集qps数据
map<string, double> serviceqps = getclusterqps();
// 生成ascii艺术报表
stringbuilder dashboard = new stringbuilder();
dashboard.append("\n");
dashboard.append("╔══════════════════════════════════════════╗\n");
dashboard.append("║ 系统qps监控大屏 ║\n");
dashboard.append("╠══════════════════════════════════════════╣\n");
serviceqps.foreach((service, qps) -> {
// 生成进度条
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");
// 添加系统健康建议
dashboard.append("\n 系统建议:\n");
double maxqps = serviceqps.values().stream().max(double::compare).orelse(0.0);
if (maxqps < 50) {
dashboard.append(" 系统有点闲,可以考虑接点私活~ \n");
} else if (maxqps > 1000) {
dashboard.append(" 系统快冒烟了!快加机器!\n");
} else {
dashboard.append(" 状态完美,继续保持!\n");
}
return dashboard.tostring();
}
// 定时推送qps警告
@scheduled(fixedrate = 60000)
public void checkqpsalert() {
map<string, double> currentqps = getclusterqps();
currentqps.foreach((service, qps) -> {
if (qps > 1000) {
log.error("救命!{}服务qps爆表了:{},快看看是不是被爬了!",
service, qps);
// 这里可以接入钉钉/企业微信告警
sendalerttodingtalk(service, qps);
}
});
}
private void sendalerttodingtalk(string service, double qps) {
string message = string.format(
"{\"msgtype\": \"text\", \"text\": {\"content\": \"%s服务qps异常:%.1f,快去看看吧!\"}}",
service, qps
);
// 调用钉钉webhook
log.warn("已发送钉钉告警:{}", message);
}
}
三、方案详细实施步骤
方案1实施步骤(简易版):
- 添加依赖:给你的
pom.xml来点"维生素"
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-aop</artifactid>
</dependency>
- 启用aop:在主类上贴个"创可贴"
@springbootapplication
@enableaspectjautoproxy // 启用aop魔法
public class application {
public static void main(string[] args) {
springapplication.run(application.class, args);
system.out.println("qps监控小鸡破壳而出!");
}
}
- 创建切面类:如上文的
qpsmonitoraspect - 测试一下:疯狂刷新接口,看看控制台输出
方案2实施步骤(专业版):
- 添加全家桶依赖:
<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
- 访问监控数据:
http://localhost:8080/actuator/metrics # 查看所有指标 http://localhost:8080/actuator/prometheus # prometheus格式
方案3实施步骤(豪华版):
- 搭建spring boot admin server:
@springbootapplication
@enableadminserver
public class adminserverapplication {
public static void main(string[] args) {
springapplication.run(adminserverapplication.class, args);
system.out.println("监控大屏已就位,陛下请检阅!");
}
}
- 客户端配置:如上文yml配置
- 访问admin ui:
http://localhost:9090
四、qps统计的进阶技巧
1. 滑动窗口统计(最近n秒的qps)
public class slidingwindowqpscounter {
// 用环形队列实现滑动窗口
private final linkedlist<long> timestamps = new linkedlist<>();
private final int windowseconds;
public slidingwindowqpscounter(int windowseconds) {
this.windowseconds = windowseconds;
log.info("创建滑动窗口监控,窗口大小:{}秒", 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. 分位数统计(p90/p95/p99响应时间)
@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("分位数统计已启用,准备精准打击慢查询!");
};
}
3. 基于qps的自动熔断
@component
public class adaptivecircuitbreaker {
private volatile boolean circuitopen = false;
private double currentqps = 0;
@scheduled(fixedrate = 1000)
public void monitorandadjust() {
// 获取当前qps
currentqps = calculatecurrentqps();
if (circuitopen && currentqps < 100) {
circuitopen = false;
log.info("熔断器关闭,系统恢复供电!当前qps: {}", currentqps);
} else if (!circuitopen && currentqps > 1000) {
circuitopen = true;
log.error("熔断器触发!qps过高: {},系统进入保护模式", currentqps);
}
// 动态调整线程池大小
adjustthreadpool(currentqps);
}
private void adjustthreadpool(double qps) {
int suggestedsize = (int) (qps * 0.5); // 经验公式
log.debug("建议线程池大小调整为: {} (基于qps: {})", suggestedsize, qps);
}
}
五、总结:给系统做qps监控就像...
1.为什么要监控qps?
- 对系统:就像给汽车装时速表,超速了会报警
- 对开发:就像给程序员装"健康手环",代码跑太快会冒烟
- 对老板:就像给公司装"业绩大屏",数字好看心情好
2.各方案选择建议:
- 初创公司/小项目:用方案1,简单粗暴见效快,就像"创可贴"
- 中型项目/微服务:用方案2,全面体检不遗漏,就像"年度体检"
- 大型分布式系统:用方案3,全景监控无死角,就像"卫星监控"
3.最佳实践提醒:
// 记住这些黄金法则:
public class qpsgoldenrules {
// 法则1:监控不是为了监控而监控
public static final string rule_1 = "别让监控把系统压垮了!";
// 法则2:告警要有意义
public static final string rule_2 = "狼来了喊多了,就没人信了!";
// 法则3:数据要可视化
public static final string rule_3 = "老板看不懂的图表都是废纸!";
// 法则4:要有应对方案
public static final string rule_4 = "光报警不解决,要你有何用?";
}
4.最后总结:
给你的系统加qps监控,就像是:
- 给外卖小哥配了计步器 —— 知道他每天跑多少
- 给程序员装了键盘计数器 —— 知道他有多卷
- 给系统装了"心电图机" —— 随时掌握生命体征
记住,一个健康的系统应该:
- 平时 心跳平稳(qps稳定)
- 大促时 适当兴奋(弹性扩容)
- 故障时 自动降压(熔断降级)
现在就去给你的springboot系统装上"智能手环"吧!让它在代码的海洋里,游得更快、更稳、更健康!
最后的最后:监控千万条,稳定第一条;qps不规范,运维两行泪!
以上就是springboot实现qps监控的完整代码的详细内容,更多关于springboot实现qps监控的资料请关注代码网其它相关文章!
发表评论