资讯动态

SpringBoot 接口性能优化,6 个手段把 QPS 提升数倍

发布时间:2026/8/13 5:30:14 来源:尧图企业网站定制
前言性能瓶颈从何而来最近在排查一个线上服务时发现一个查询用户详情的接口在并发量稍高时响应时间就从平时的 50ms 飙升到了 500ms 以上CPU 使用率也居高不下。这让我意识到很多 SpringBoot 项目在初期为了快速上线往往忽略了性能设计等到用户量上来接口就成了整个系统的“木桶短板”。性能优化不是玄学它是一系列可量化、可验证的工程实践。今天我就结合自己踩过的坑和实战经验分享 6 个经过验证的 SpringBoot 接口优化手段。这些方法从数据库、缓存、代码到架构层层递进合理运用后将接口 QPS每秒查询率提升数倍并非难事。文章会穿插代码示例方便大家直接应用到自己的项目中。1. 慢 SQL 识别与优化从源头掐住瓶颈数据库通常是性能问题的第一嫌疑人。一个未经优化的复杂联表查询足以拖垮整个接口。1.1 开启慢查询日志让问题无处遁形首先你得知道哪些 SQL 慢了。在application.yml中配置 MySQL 的慢查询日志这里以 HikariCP 连接池为例spring: datasource: hikari: >// 错误的写法会导致 N1 次查询 GetMapping(/orders) public ListOrderDTO getOrders() { ListOrder orders orderRepository.findAll(); // 1次查询获取所有订单 return orders.stream().map(order - { OrderDTO dto new OrderDTO(); dto.setId(order.getId()); // 访问关联集合每条订单都会触发一次查询订单项 dto.setItemCount(order.getItems().size()); // N次查询 return dto; }).collect(Collectors.toList()); }优化方案1使用 JOIN FETCH// 在 Repository 中定义查询方法一次查询搞定 Query(SELECT o FROM Order o LEFT JOIN FETCH o.items) ListOrder findAllWithItems();优化方案2使用 EntityGraph 注解EntityGraph(attributePaths {items}) ListOrder findAll();优化后数据库只需执行一次查询通过 JOIN 将订单和订单项数据一次性取出性能提升立竿见影。2. 引入多级缓存用空间换时间如果数据变化不频繁缓存是提升 QPS 最有效的手段之一。不要只想到 Redis合理的多级缓存策略能进一步降低延迟。2.1 本地缓存 (Caffeine) 分布式缓存 (Redis)对于极热且基本不变的数据如系统配置、城市列表可以使用本地缓存访问速度是纳秒级。Configuration EnableCaching public class CacheConfig { Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager new CaffeineCacheManager(); // 配置 Caffeine最大1000条写入后1小时过期 cacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.HOURS) .recordStats()); // 开启统计方便监控 return cacheManager; } } Service public class ProductService { Cacheable(value products, key #id) public Product getProductById(Long id) { // 模拟数据库查询 return productRepository.findById(id).orElseThrow(); } Cacheable(value hotProducts, key list) public ListProduct getHotProducts() { // 查询热门商品列表 return productRepository.findTop10ByOrderBySalesDesc(); } }对于需要跨服务共享或数据量较大的缓存则使用 Redis。可以使用 Spring Cache 抽象层通过注解灵活配置。Configuration public class RedisCacheConfig { Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) // 默认30分钟过期 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // 针对特定缓存名做个性化配置 MapString, RedisCacheConfiguration cacheConfigs new HashMap(); cacheConfigs.put(products, config.entryTtl(Duration.ofHours(2))); // 商品缓存2小时 return RedisCacheManager.builder(factory) .cacheDefaults(config) .withInitialCacheConfigurations(cacheConfigs) .build(); } }2.2 缓存穿透、击穿、雪崩的应对策略穿透查询一个不存在的数据。方案缓存空值设置较短TTL或使用布隆过滤器提前拦截。击穿某个热点 key 过期瞬间大量请求打到 DB。方案使用互斥锁Redis setnx或逻辑过期时间。雪崩大量 key 同时过期。方案给缓存过期时间加上随机值。// 示例使用互斥锁解决缓存击穿 public Product getProductWithLock(Long id) { String cacheKey product: id; Product product redisTemplate.opsForValue().get(cacheKey); if (product ! null) { return product; } // 尝试获取分布式锁 String lockKey lock:product: id; boolean locked false; try { locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (locked) { // 拿到锁查数据库并重建缓存 product productRepository.findById(id).orElse(null); if (product ! null) { redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS); } else { // 防止穿透缓存空值5分钟 redisTemplate.opsForValue().set(cacheKey, new NullValue(), 5, TimeUnit.MINUTES); } return product; } else { // 没拿到锁短暂休眠后重试 Thread.sleep(50); return getProductWithLock(id); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(获取产品信息中断, e); } finally { if (locked) { redisTemplate.delete(lockKey); } } }3. 异步化与并行处理别让请求排队对于耗时操作如发送短信、生成报表、调用外部接口异步化可以立即释放请求线程显著提高接口吞吐量。3.1 使用 Async 实现简单异步Configuration EnableAsync public class AsyncConfig implements AsyncConfigurer { Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix(Async-); executor.initialize(); return executor; } } Service public class NotificationService { Async // 该方法将在线程池中执行 public CompletableFutureVoid sendSms(String phone, String content) { // 模拟耗时操作 try { Thread.sleep(2000); System.out.println(短信已发送至 phone); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return CompletableFuture.completedFuture(null); } } RestController public class OrderController { Autowired private NotificationService notificationService; PostMapping(/order) public ResponseEntityString createOrder(RequestBody Order order) { // 1. 同步处理创建订单核心业务 orderService.create(order); // 2. 异步处理发送通知非核心可容忍延迟 notificationService.sendSms(order.getUserPhone(), 您的订单已创建成功); return ResponseEntity.ok(订单创建成功); } }3.2 使用 CompletableFuture 进行并行调用当接口需要调用多个独立的第三方服务时并行化可以大幅缩短总响应时间。public OrderDetail getOrderDetail(Long orderId) { CompletableFutureOrder orderFuture CompletableFuture.supplyAsync(() - orderService.getById(orderId)); CompletableFutureListOrderItem itemsFuture CompletableFuture.supplyAsync(() - orderItemService.getByOrderId(orderId)); CompletableFutureUser userFuture CompletableFuture.supplyAsync(() - userService.getById(order.getUserId())); try { // 等待所有并行任务完成 Order order orderFuture.get(3, TimeUnit.SECONDS); ListOrderItem items itemsFuture.get(3, TimeUnit.SECONDS); User user userFuture.get(3, TimeUnit.SECONDS); OrderDetail detail new OrderDetail(); detail.setOrder(order); detail.setItems(items); detail.setUser(user); return detail; } catch (Exception e) { throw new RuntimeException(获取订单详情失败, e); } }4. 连接池与线程池调优合理利用资源不合理的池化配置会导致资源浪费或成为瓶颈。4.1 数据库连接池 (HikariCP) 配置示例spring: datasource: hikari: # 连接池大小 ((core_count * 2) effective_spindle_count) # 对于4核SSD的服务器建议值在 10-20 之间 maximum-pool-size: 15 minimum-idle: 5 # 连接最大存活时间防止长时间空闲连接出现问题 max-lifetime: 1800000 # 30分钟 # 连接超时时间 connection-timeout: 30000 # 30秒 # 验证连接是否有效的SQL connection-test-query: SELECT 1 # 空闲连接超时时间 idle-timeout: 600000 # 10分钟4.2 Web 服务器线程池 (Tomcat) 配置server: tomcat: # 最大线程数决定了并发处理能力 max-threads: 200 # 最小工作线程数 min-spare-threads: 10 # 等待队列长度 accept-count: 100 # 连接超时 connection-timeout: 20000经验之谈线程数并非越多越好。过多的线程会导致频繁的上下文切换反而降低性能。可以通过监控工具如 Arthas、Prometheus观察线程活跃数和 CPU 负载来调整。5. 序列化与传输优化减少网络开销对于返回大量数据的接口如列表查询序列化和网络传输可能成为瓶颈。5.1 使用更高效的序列化方式默认的 JSON 序列化Jackson虽然通用但性能并非最优。对于内部微服务调用可以考虑 Protobuf、Kryo 或 Hessian。// 示例使用 FastJson 作为 HttpMessageConverter需谨慎评估安全风险 Configuration public class WebConfig implements WebMvcConfigurer { Override public void configureMessageConverters(ListHttpMessageConverter? converters) { // 将 FastJson 放在前面优先使用 FastJsonHttpMessageConverter converter new FastJsonHttpMessageConverter(); FastJsonConfig config new FastJsonConfig(); config.setSerializerFeatures( SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.DisableCircularReferenceDetect // 禁用循环引用检测提升性能 ); converter.setFastJsonConfig(config); converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON)); converters.add(0, converter); } }5.2 启用 HTTP 压缩对于文本类响应JSON、HTML启用 GZIP 压缩可以显著减少传输体积。server: compression: enabled: true # 对以下MIME类型进行压缩 mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml # 响应大小超过此值才压缩 min-response-size: 10245.3 分页查询避免一次性拉取大量数据这是老生常谈但至关重要的一点。务必在查询列表的接口中强制使用分页。GetMapping(/products) public PageProduct getProducts( RequestParam(defaultValue 0) int page, RequestParam(defaultValue 20) int size) { // 使用 Spring Data JPA 的分页对象 Pageable pageable PageRequest.of(page, size, Sort.by(createTime).descending()); return productRepository.findAll(pageable); }6. 监控与持续优化让优化有据可依没有度量就没有优化。必须建立监控体系才能发现潜在问题并验证优化效果。6.1 集成 Micrometer 暴露指标!-- pom.xml 依赖 -- dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency# application.yml management: endpoints: web: exposure: include: health,info,prometheus,metrics metrics: export: prometheus: enabled: true访问/actuator/prometheus即可获取格式化的监控指标。6.2 关键监控指标接口层面QPS、平均响应时间 (avg_rt)、P95/P99 响应时间、错误率。系统层面CPU 使用率、内存使用率、GC 频率与耗时。中间件层面数据库连接池活跃数、Redis 命中率、线程池队列大小。可以使用 Grafana 配置仪表盘将上述指标可视化设置告警规则。6.3 性能测试与对比优化前后务必使用 JMeter 或 Gatling 进行压测对比。记录关键数据优化前QPS 100平均 RT 200msCPU 80%。优化手段1加索引QPS 提升至 180平均 RT 降至 120ms。优化手段2加缓存QPS 提升至 500平均 RT 降至 30ms。用数据说话才能证明优化的价值。总结与避坑指南回顾一下这 6 个核心手段慢 SQL 优化是基础多级缓存是利器异步

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价