平时做项目的时候,尤其是遇到电商大促、秒杀活动这种场景,经常会碰到应用扛不住大量请求的问题——一会儿是接口响应超时,一会儿是系统直接报错,用户刷半天都看不到结果,甚至连应用服务都挂了。这时候,给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 不同策略的优缺点分析
每个调优策略都有适用场景,也有局限性:
- 线程池调优:优点是快速提升请求处理能力,不用改业务代码;缺点是设太大线程数会浪费内存,要结合服务器配置。
- 连接池优化:优点是直接解决数据库连接不足的问题;缺点是不能超过数据库的上限,要和数据库管理员配合调整。
- 异步处理:优点是缩短接口响应时间,提升吞吐量;缺点是非核心任务失败不会立刻发现,要做失败告警。
- 缓存使用:优点是大幅降低数据库压力,响应快;缺点是增加了缓存的运维成本,还要处理数据一致性问题。
3.2 日常调优的注意事项
调优不是拍脑袋改配置,要注意三个关键点: 第一,必须做压测:不管调什么参数,都要先用JMeter或者Spring Boot Actuator做压测,看实际的响应时间、吞吐量,不能凭感觉。 第二,要监控指标:用Prometheus+Grafana监控线程数、连接数、缓存命中率这些指标,实时发现问题,比如线程数突然飙升,说明配置可能不够。 第三,循序渐进:调优要小范围测试,比如先在测试环境改,再在预生产环境测,最后才上线,不能全量一下子改,避免出问题影响用户。
四、总结
Spring Boot应对高并发的性能调优,核心是把资源用在刀刃上:从线程池调整处理请求的速度,从数据库连接池减少数据库压力,用异步缩短核心接口的响应时间,用缓存降低数据库的查询量。这些策略不需要复杂的框架改造,只要改几个配置或者加几个注解就能落地,适合不同基础的开发者。最后要记住,调优是持续的过程,要结合业务场景、服务器配置和压测结果不断调整,才能保证应用在高并发下稳定运行。
评论
围绕“Spring Boot性能调优策略,应对高并发场景”参与讨论