平时做项目的时候,尤其是遇到电商大促、秒杀活动这种场景,经常会碰到应用扛不住大量请求的问题——一会儿是接口响应超时,一会儿是系统直接报错,用户刷半天都看不到结果,甚至连应用服务都挂了。这时候,给Spring Boot应用做性能调优,应对高并发场景就成了必须要解决的问题。

一、了解高并发下Spring Boot的“痛点”

高并发场景不是抽象概念,而是实实在在的业务压力:比如电商秒杀的前5分钟,同一时间内几十万用户同时抢一款限量商品;或者直播平台的弹幕功能,十几万人同时发消息。这种场景下,应用的各个环节都可能出问题,常见的痛点有三个:

1.1 常见的性能问题表现

最直观的就是接口响应慢,用户要等好几秒才能拿到结果,甚至直接提示“服务繁忙”;然后是系统内部的错误,比如线程池满了,新的请求没地方放;或者数据库连接不够,查数据的时候直接超时。这些问题本质上都是应用的资源没跟上业务的压力。

1.2 为什么默认配置扛不住高并发

Spring Boot的默认配置是通用的,不是为高并发设计的——比如Tomcat默认核心线程数只有20,最大线程数200;数据库连接池默认最大连接数只有10,就像小餐馆只配了2个服务员和10张桌子,来了100个客人肯定要排队甚至坐不下,根本扛不住高并发的冲击。

二、核心调优策略

针对上面的痛点,我们可以从四个核心方向入手,每个方向都有具体的可落地的示例,直接改就能用,不用复杂的改造。

2.1 内置容器的线程池合理配置

内置容器(比如Tomcat)是处理请求的第一道关卡,线程池的大小直接决定了能同时处理多少请求。默认的线程数太小,高并发下会大量排队,所以要调整核心线程数和最大线程数,同时设置合理的队列和超时时间。

# application.properties里的Tomcat线程池配置,针对高并发场景调整
server.tomcat.core-threads = 100 # 核心线程数:一直存活的线程,用来处理日常请求,不用重复创建,提升响应速度
server.tomcat.max-threads = 800 # 最大线程数:高并发时新增的线程上限,参考服务器CPU核心数,16核服务器设置800左右(核心数*5)
server.tomcat.accept-count = 500 # 请求队列等待数:当所有线程都在忙时,超出的请求先排队,队列满了才会拒绝,避免直接报错
server.tomcat.connection-timeout = 5000 # 连接超时时间:单位毫秒,超过这个时间还没建立连接就断掉,防止无效连接占资源

调整的时候要注意:最大线程数不能太大,比如如果设成几千,线程切换的开销会抵消掉性能提升,反而变慢,要根据服务器配置和压测结果来定。

2.2 数据库连接池优化

数据库是高并发下另一个瓶颈,连接池太小不够用,太大又会给数据库带来压力。Spring Boot默认用HikariCP连接池,这个池的性能很好,只要调整几个参数就能适配高并发。

# application.yml里的HikariCP数据库连接池配置
spring:
  datasource:
    hikari:
      maximum-pool-size: 50 # 最大连接数:必须和MySQL的max_connections匹配,MySQL默认最大连接数是151,设置50既能满足业务,又不会给数据库太大压力
      idle-timeout: 30000 # 空闲连接超时时间:30秒没用的连接就释放,避免长期占着连接浪费资源
      connection-timeout: 2000 # 获取连接的超时时间:2秒拿不到连接就报错,防止应用一直等待卡住

这里的关键是:数据库的连接数是有限的,比如MySQL的max_connections如果改到100,那连接池设50就安全,不能超过数据库能承受的上限,不然数据库会挂,整个应用也会崩。

2.3 非核心接口的异步化处理

很多业务接口里有非核心步骤,比如发短信、发消息、统计日志这些,这些步骤不用等结果,也不会影响主流程的成功。把这些步骤改成异步处理,能大幅缩短接口的响应时间,提升高并发下的吞吐量。 首先要开启Spring的异步支持,然后自定义异步线程池,避免和主线程池冲突:

// 自定义异步线程池配置,Spring Boot专用,放在config包下
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync // 必须加这个注解,开启异步任务的支持
public class AsyncConfig {

    // 自定义异步线程池,专门处理非核心任务(比如发短信、发通知)
    @Bean("asyncTaskExecutor")
    public Executor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // 核心线程数:处理日常的异步任务,不用重复创建
        executor.setMaxPoolSize(20); // 最大线程数:高并发异步任务时的上限,不会随便新建太多线程
        executor.setQueueCapacity(100); // 任务队列:异步任务先放队列里,满了才新建线程
        executor.setThreadNamePrefix("async-task-"); // 线程名前缀,排查问题的时候能一眼认出是异步线程
        executor.initialize(); // 初始化线程池
        return executor;
    }
}

业务代码里的使用示例,比如创建订单时发短信不用等:

// 订单服务,处理下单逻辑
@Service
public class OrderService {

    // 注入自定义的异步线程池,和主线程隔离
    @Autowired
    @Qualifier("asyncTaskExecutor")
    private Executor asyncTaskExecutor;

    // 核心下单逻辑,同步处理,必须要等结果,出错要回滚
    public String createOrder(OrderDTO dto) {
        // 1. 减库存:核心步骤,必须同步
        stockService.reduceStock(dto.getProductId(), dto.getCount());
        // 2. 扣用户余额:核心步骤,必须同步
        accountService.reduceBalance(dto.getUserId(), dto.getAmount());
        // 3. 发送下单成功短信:非核心,异步处理,不影响下单结果
        CompletableFuture.runAsync(() -> sendSms(dto.getPhone()), asyncTaskExecutor);
        return "订单创建成功";
    }

    // 异步执行的短信发送方法
    private void sendSms(String phone) {
        // 调用短信服务商的API,比如阿里云短信,这里模拟实际调用
        System.out.println("给手机号" + phone + "发送下单成功短信");
    }
}

这样改完之后,接口的响应时间只取决于核心步骤的耗时,异步的短信不会拖后腿,高并发下每秒能处理的请求数会翻倍。

2.4 缓存的合理使用(减少数据库压力)

高并发下,数据库的压力是最大的,大部分请求其实不需要实时查数据库,比如商品详情、用户信息这些读多写少的数据,可以用缓存存起来,这样大部分请求直接走缓存,不用查数据库,压力就小很多。Spring Boot里用Redis做缓存很简单,只要加几个注解就行。

// 商品服务,用缓存优化查询性能
@Service
public class ProductService {

    @Autowired
    private ProductMapper productMapper; // 数据库查询的Mapper

    // 缓存商品详情,key是product::商品ID,过期时间10分钟,只有缓存不存在的时候才查数据库
    @Cacheable(value = "product", key = "#id", expire = 600)
    public Product getProductById(Long id) {
        // 这里的代码只有缓存没命中的时候才会执行,平时直接返回缓存里的结果
        return productMapper.selectById(id);
    }

    // 商品更新后,清除对应的缓存,避免读到旧数据,保证一致性
    @CacheEvict(value = "product", key = "#id")
    public void updateProduct(Long id, Product product) {
        productMapper.updateById(product); // 更新数据库
    }
}

用缓存的时候要注意:不是所有数据都能缓存,比如实时性要求很高的订单数据就不能用;还要控制缓存的过期时间,太长会有旧数据,太短起不到效果;更要注意缓存的一致性,更新数据后一定要清除缓存,不然会出问题。

三、调优的实战效果与注意事项

3.1 不同策略的优缺点分析

每个调优策略都有适用场景,也有局限性:

  1. 线程池调优:优点是快速提升请求处理能力,不用改业务代码;缺点是设太大线程数会浪费内存,要结合服务器配置。
  2. 连接池优化:优点是直接解决数据库连接不足的问题;缺点是不能超过数据库的上限,要和数据库管理员配合调整。
  3. 异步处理:优点是缩短接口响应时间,提升吞吐量;缺点是非核心任务失败不会立刻发现,要做失败告警。
  4. 缓存使用:优点是大幅降低数据库压力,响应快;缺点是增加了缓存的运维成本,还要处理数据一致性问题。

3.2 日常调优的注意事项

调优不是拍脑袋改配置,要注意三个关键点: 第一,必须做压测:不管调什么参数,都要先用JMeter或者Spring Boot Actuator做压测,看实际的响应时间、吞吐量,不能凭感觉。 第二,要监控指标:用Prometheus+Grafana监控线程数、连接数、缓存命中率这些指标,实时发现问题,比如线程数突然飙升,说明配置可能不够。 第三,循序渐进:调优要小范围测试,比如先在测试环境改,再在预生产环境测,最后才上线,不能全量一下子改,避免出问题影响用户。

四、总结

Spring Boot应对高并发的性能调优,核心是把资源用在刀刃上:从线程池调整处理请求的速度,从数据库连接池减少数据库压力,用异步缩短核心接口的响应时间,用缓存降低数据库的查询量。这些策略不需要复杂的框架改造,只要改几个配置或者加几个注解就能落地,适合不同基础的开发者。最后要记住,调优是持续的过程,要结合业务场景、服务器配置和压测结果不断调整,才能保证应用在高并发下稳定运行。