当前位置: 代码网 > it编程>编程语言>Java > SpringBoot实现QPS监控的完整代码

SpringBoot实现QPS监控的完整代码

2025年12月10日 Java 我要评论
一、什么是qps?—— 系统的"心跳频率"想象一下你的系统就像一个忙碌的外卖小哥,qps(query per second)就是他每秒能送多少份外卖!如果小

一、什么是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实施步骤(简易版):

  1. 添加依赖:给你的pom.xml来点"维生素"
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-aop</artifactid>
</dependency>
  1. 启用aop:在主类上贴个"创可贴"
@springbootapplication
@enableaspectjautoproxy  // 启用aop魔法
public class application {
    public static void main(string[] args) {
        springapplication.run(application.class, args);
        system.out.println("qps监控小鸡破壳而出!");
    }
}
  1. 创建切面类:如上文的qpsmonitoraspect
  2. 测试一下:疯狂刷新接口,看看控制台输出

方案2实施步骤(专业版):

  1. 添加全家桶依赖
<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>
  1. 配置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
  1. 访问监控数据
http://localhost:8080/actuator/metrics  # 查看所有指标
http://localhost:8080/actuator/prometheus  # prometheus格式

方案3实施步骤(豪华版):

  1. 搭建spring boot admin server
@springbootapplication
@enableadminserver
public class adminserverapplication {
    public static void main(string[] args) {
        springapplication.run(adminserverapplication.class, args);
        system.out.println("监控大屏已就位,陛下请检阅!");
    }
}
  1. 客户端配置:如上文yml配置
  2. 访问admin uihttp://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监控的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com