SpringBoot实现QPS监控的完整代码

 更新时间:2025年12月10日 10:04:17   作者:悟空码字  
本文介绍了QPS(每秒查询率)的概念,并讨论了三种监控方案:简易版(AOP拦截器)、专业版(Micrometer+Prometheus)和豪华版(SpringBootAdmin),每种方案都有详细的实施步骤,文章还介绍了QPS统计的进阶技巧,并强调了监控系统的重要性,需要的朋友可以参考下

一、什么是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监控的资料请关注脚本之家其它相关文章!

相关文章

  • Eclipse中常用快捷键汇总

    Eclipse中常用快捷键汇总

    这篇文章主要介绍了Eclipse中常用快捷键,文中介绍的非常详细,帮助大家更好的利用eclipse开发,感兴趣的朋友可以了解下
    2020-07-07
  • java字符串相似度算法

    java字符串相似度算法

    这篇文章主要介绍了java字符串相似度算法,是Java实现比较典型的算法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Java实现Word转PDF的全过程

    Java实现Word转PDF的全过程

    在IT领域,文档格式转换是常见的任务之一,特别是在管理大量文本数据时,本文将详细探讨如何利用Java技术将Word文档(.docx)转换成PDF格式,需要的朋友可以参考下
    2025-04-04
  • 详解SpringBoot异常处理流程及原理

    详解SpringBoot异常处理流程及原理

    今天给大家带来的是关于Java的相关知识,文章围绕着SpringBoot异常处理流程及原理展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • SpringBoot自定义MessageConvert详细讲解

    SpringBoot自定义MessageConvert详细讲解

    正在学习SpringBoot,在自定义MessageConverter时发现:为同一个返回值类型配置多个MessageConverter时,可能会发生响应数据格式错误,或406异常(客户端无法接收相应数据)。在此记录一下解决问题以及追踪源码的过程
    2023-01-01
  • SpringBoot利用模板实现自动生成Word合同的功能

    SpringBoot利用模板实现自动生成Word合同的功能

    这篇文章主要为大家详细介绍了SpringBoot如何利用模板实现自动生成Word合同的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下
    2025-12-12
  • Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤详解

    Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤详解

    这篇文章主要介绍了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • SpringBoot声明式事务的简单运用说明

    SpringBoot声明式事务的简单运用说明

    这篇文章主要介绍了SpringBoot声明式事务的简单运用说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • JavaGUI常用三种布局使用介绍

    JavaGUI常用三种布局使用介绍

    这篇文章主要介绍了JavaGUI常用三种布局-FlowLayout、BorderLayout、GridLayout,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-03-03
  • Maven里面没有plugins dependence问题解决

    Maven里面没有plugins dependence问题解决

    在整合Nacos和Dubbo时,出现Maven错误可以通过检查父模块的依赖解决,问题源于MySQL驱动版本不兼容,移除特定依赖并刷新pom文件可恢复项目,执行clean命令,查看报错,感兴趣的可以了解一下
    2024-10-10

最新评论