《SpringBoot性能调优:从10秒到100毫秒的实战技巧》
每天5分钟,掌握一个SpringBoot核心知识点。大家好,我是SpringBoot指南的小坏。前几天我们聊了日志、监控、健康检查,今天来点更刺激的——让你的SpringBoot应用飞起来!
一、先看一个真实案例
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
我朋友的公司,去年上线了一个新系统:
上线前测试:响应时间 200毫秒
上线第一天:响应时间 800毫秒
一个月后:响应时间 5秒
三个月后:用户投诉,响应时间 10秒
老板下了死命令:“一周内优化到500毫秒内!”
优化过程:
- 第1天:加缓存 → 降到8秒
- 第2天:优化SQL → 降到3秒
- 第3天:加索引 → 降到1秒
- 第4天:线程池调优 → 降到500毫秒
- 第5天:JVM调优 → 降到200毫秒
最终效果:
- 服务器从20台降到8台
- 每月节省服务器成本:6万元
- 用户满意度从40%提升到95%
今天,我就把这一整套优化方案分享给你!
二、第一步:找到瓶颈在哪
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
优化前先别急着改代码,先搞清楚慢在哪。
2.1 最简单的性能测试
在pom.xml里加个依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在application.yml里加配置:
management:
endpoints:
web:
exposure:
include: metrics,httptrace,prometheus
访问:http://localhost:8080/actuator/metrics/http.server.requests
你会看到类似的数据:
{
"请求总数": 15234,
"平均响应时间": "5.2s", // 太慢了!
"最大响应时间": "23.1s", // 有接口特别慢
"错误率": "3.2%"
}
2.2 找出最慢的接口
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。 用这个命令找最慢的接口:
# 查看所有接口响应时间排序
curl http://localhost:8080/actuator/metrics/http.server.requests | jq '.measurements[] | select(.statistic=="TOTAL_TIME")'
通常会发现问题集中在:
- 查询用户列表(关联5张表,没分页)
- 导出Excel(一次查10万条数据)
- 第三方接口调用(没设超时时间)
三、数据库优化:80%的慢都是SQL的锅
3.1 先看慢查询日志
MySQL开启慢查询:
-- 查看当前配置
SHOW VARIABLES LIKE '%slow_query%';
-- 开启慢查询日志(生产环境慎用)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过1秒就算慢
-- 查看慢查询
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;
3.2 最常见的SQL优化场景
场景1:没有索引
-- ❌ 错误:100万条数据全表扫描
SELECT * FROM users WHERE phone = '13800138000';
-- ✅ 正确:加索引
ALTER TABLE users ADD INDEX idx_phone (phone);
-- 查询时间:从2秒 → 0.02秒
场景2:分页查询太深
-- ❌ 错误:查第10000页
SELECT * FROM orders LIMIT 10000, 20; -- 扫描10020条
-- ✅ 正确:用id分页
SELECT * FROM orders WHERE id > 10000 ORDER BY id LIMIT 20;
-- 查询时间:从1.5秒 → 0.01秒
场景3:关联查询太多
// ❌ 错误:一次关联5张表
@Query("SELECT o FROM Order o " +
"JOIN FETCH o.user u " +
"JOIN FETCH o.address a " +
"JOIN FETCH o.items i " +
"JOIN FETCH i.product p " +
"WHERE o.id = :id")
Order findOrderWithDetails(@Param("id") Long id);
// ✅ 正确:分批查询或只查需要字段
@Query("SELECT new OrderDTO(o.id, o.status, u.name) " +
"FROM Order o JOIN o.user u " +
"WHERE o.id = :id")
OrderDTO findOrderBasic(@Param("id") Long id);
3.3 连接池配置
连接池配置不对,性能直接掉一半:
# application.yml
spring:
datasource:
hikari:
# 连接池大小 = (核心数 * 2) + 有效磁盘数
maximum-pool-size: 20 # 最大连接数
minimum-idle: 10 # 最小空闲连接
connection-timeout: 30000 # 连接超时30秒
idle-timeout: 600000 # 空闲连接10分钟后关闭
max-lifetime: 1800000 # 连接最大生存时间30分钟
# 性能关键参数
connection-test-query: SELECT 1 # 测试连接有效性的SQL
validation-timeout: 5000 # 验证超时5秒
四、缓存优化:让响应快10倍
4.1 加Redis缓存
先加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
最简单的缓存:
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private RedisTemplate<String, Product> redisTemplate;
// 加缓存:商品详情
public Product getProduct(Long id) {
String key = "product:" + id;
// 1. 先查缓存
Product product = redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 2. 缓存没有,查数据库
product = productRepository.findById(id).orElse(null);
if (product != null) {
// 3. 写入缓存,设置30分钟过期
redisTemplate.opsForValue().set(
key,
product,
30, TimeUnit.MINUTES
);
}
return product;
}
}
效果:
- 第一次查询:数据库 50毫秒
- 后续查询:Redis 5毫秒
- 性能提升:10倍!
4.2 Spring Cache注解(更简单)
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
@Configuration
@EnableCaching // 开启缓存
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))) // 默认30分钟
.build();
}
}
// 使用缓存
@Service
public class UserService {
@Cacheable(value = "users", key = "#id") // 自动缓存
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
@CachePut(value = "users", key = "#user.id") // 更新缓存
public User updateUser(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "users", key = "#id") // 删除缓存
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
4.3 缓存雪崩/穿透/击穿解决方案
@Service
public class SafeCacheService {
// 1. 缓存雪崩:大量缓存同时过期
// 解决:设置不同的过期时间
public void setWithRandomExpire(String key, Object value) {
// 基础30分钟 + 随机0-5分钟
int randomMinutes = new Random().nextInt(5);
redisTemplate.opsForValue().set(
key, value,
30 + randomMinutes, TimeUnit.MINUTES
);
}
// 2. 缓存穿透:查询不存在的数据
// 解决:缓存空值
public Product getProductSafely(Long id) {
String key = "product:" + id;
Product product = redisTemplate.opsForValue().get(key);
if (product != null) {
// 如果是特殊标记的空值,直接返回null
if (product.getId() == -1) {
return null;
}
return product;
}
// 查数据库
product = productRepository.findById(id).orElse(null);
if (product == null) {
// 缓存空值,防止穿透
Product emptyMarker = new Product();
emptyMarker.setId(-1L);
redisTemplate.opsForValue().set(key, emptyMarker, 5, TimeUnit.MINUTES);
return null;
}
// 缓存正常数据
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
return product;
}
// 3. 缓存击穿:热点key过期
// 解决:互斥锁
public Product getProductWithLock(Long id) {
String key = "product:" + id;
String lockKey = "lock:product:" + id;
// 先查缓存
Product product = redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 尝试获取锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
try {
if (locked != null && locked) {
// 拿到锁,查数据库
product = productRepository.findById(id).orElse(null);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
} else {
// 没拿到锁,等待100毫秒后重试
Thread.sleep(100);
return getProductWithLock(id);
}
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
return product;
}
}
五、JVM调优:让内存更聪明
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
5.1 查看当前JVM状态
启动时加参数:
java -jar app.jar -Xmx2g -Xms2g -XX:+PrintGCDetails
或者用命令行工具:
# 查看进程
jps -l
# 查看堆内存
jmap -heap <pid>
# 查看GC情况
jstat -gc <pid> 1000 10 # 每秒看一次,看10次
5.2 常用JVM参数
# 启动脚本 start.sh
#!/bin/bash
java -jar app.jar \
-Xmx2g -Xms2g \ # 堆内存2G
-Xmn1g \ # 新生代1G
-XX:MaxMetaspaceSize=256m \ # 元空间256M
-XX:MaxDirectMemorySize=256m \ # 直接内存256M
-XX:+UseG1GC \ # 使用G1垃圾回收器
-XX:MaxGCPauseMillis=200 \ # 目标停顿时间200ms
-XX:+PrintGCDetails \ # 打印GC详情
-XX:+PrintGCDateStamps \
-Xloggc:logs/gc.log \ # GC日志文件
-XX:+HeapDumpOnOutOfMemoryError \ # OOM时生成dump
-XX:HeapDumpPath=logs/heapdump.hprof
5.3 内存泄漏排查
如果发现内存一直增长,可能是内存泄漏:
# 1. 生成堆内存快照
jmap -dump:live,format=b,file=heap.hprof <pid>
# 2. 用MAT工具分析(下载地址:https://eclipse.dev/mat/)
# 打开heap.hprof,看哪个对象占用最多
常见内存泄漏场景:
- 静态集合:
static Map一直往里放对象 - 连接未关闭:数据库连接、文件流
- 监听器未注销:注册了监听器但没移除
- 内部类引用外部类:匿名内部类持有外部类引用
六、异步处理:别让用户等你
6.1 线程池配置
@Configuration
public class ThreadPoolConfig {
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 = CPU核心数
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
// 最大线程数 = 核心数 * 2
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 队列容量
executor.setQueueCapacity(1000);
// 线程名前缀
executor.setThreadNamePrefix("async-task-");
// 拒绝策略:调用者运行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
// 使用异步
@Service
public class OrderService {
@Async("taskExecutor") // 指定线程池
public CompletableFuture<Order> createOrderAsync(OrderRequest request) {
// 耗时操作:发邮件、发短信、写日志
sendEmail(request.getEmail());
sendSms(request.getPhone());
// 返回结果
Order order = processOrder(request);
return CompletableFuture.completedFuture(order);
}
}
6.2 @Async常见问题
问题1:同一个类内调用不生效
// ❌ 错误:同一个类内调用
@Service
public class UserService {
public void updateUser(User user) {
// 同一个类内调用,不会异步
sendNotification(user);
}
@Async
public void sendNotification(User user) {
// 不会异步执行!
}
}
// ✅ 正确:通过代理调用
@Service
public class UserService {
@Autowired
private UserService self; // 注入自己
public void updateUser(User user) {
// 通过代理调用
self.sendNotification(user); // 这会异步执行
}
@Async
public void sendNotification(User user) {
// 异步执行
}
}
问题2:需要等待所有异步任务完成
@Service
public class BatchService {
@Async
public CompletableFuture<String> processItem(String item) {
// 处理单个项目
return CompletableFuture.completedFuture("processed:" + item);
}
public void processAll(List<String> items) {
List<CompletableFuture<String>> futures = items.stream()
.map(this::processItem)
.collect(Collectors.toList());
// 等待所有任务完成
CompletableFuture<Void> allDone = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
);
// 获取所有结果
List<String> results = allDone.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
).join();
System.out.println("处理完成,共" + results.size() + "条");
}
}
七、HTTP优化:网络传输更高效
7.1 启用Gzip压缩
# application.yml
server:
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024 # 大于1KB才压缩
效果:
- JSON响应大小:从100KB → 20KB
- 传输时间:减少80%
7.2 连接池配置
# RestTemplate连接池
http:
client:
max-total: 200 # 最大连接数
default-max-per-route: 50 # 每个路由最大连接数
connect-timeout: 5000 # 连接超时5秒
read-timeout: 10000 # 读取超时10秒
# Feign客户端配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: basic
7.3 启用HTTP/2
server:
http2:
enabled: true
HTTP/2优势:
- 多路复用:一个连接同时传输多个请求
- 头部压缩:减少传输数据量
- 服务器推送:服务端可以主动推送资源
八、实战案例:电商系统优化全流程
假设有一个查询用户订单的接口:
优化前:15秒
// 优化前代码
@GetMapping("/user/{userId}/orders")
public List<Order> getUserOrders(@PathVariable Long userId) {
// 1. 查用户信息(1秒)
User user = userRepository.findById(userId);
// 2. 查所有订单(5秒,没分页)
List<Order> orders = orderRepository.findByUserId(userId);
// 3. 查每个订单的商品(N+1查询)
for (Order order : orders) {
List<OrderItem> items = orderItemRepository.findByOrderId(order.getId());
order.setItems(items);
}
// 4. 查每个商品的详情(又是N+1)
for (Order order : orders) {
for (OrderItem item : order.getItems()) {
Product product = productRepository.findById(item.getProductId());
item.setProduct(product);
}
}
return orders; // 总耗时:15秒!
}
优化后:200毫秒
// 优化后代码
@Service
public class OrderService {
@Cacheable(value = "userOrders", key = "#userId + ':' + #page + ':' + #size")
public Page<OrderVO> getUserOrders(Long userId, int page, int size) {
// 1. 只查需要字段 + 分页(100毫秒 → 10毫秒)
Page<Order> orders = orderRepository.findSimpleOrders(userId, PageRequest.of(page, size));
// 2. 批量查询商品,避免N+1(5秒 → 50毫秒)
List<Long> productIds = orders.stream()
.flatMap(order -> order.getItems().stream())
.map(OrderItem::getProductId)
.distinct()
.collect(Collectors.toList());
Map<Long, Product> productMap = productRepository.findByIdIn(productIds)
.stream()
.collect(Collectors.toMap(Product::getId, p -> p));
// 3. 组装VO,不返回全部字段(传输数据减少80%)
return orders.map(order -> convertToVO(order, productMap));
}
@Async
@CacheEvict(value = "userOrders", key = "#userId + '*'") // 清除相关缓存
public void refreshUserOrders(Long userId) {
// 异步刷新缓存
}
}
// 对应的SQL优化
public interface OrderRepository extends JpaRepository<Order, Long> {
// 优化前:SELECT * FROM orders WHERE user_id = ?
// 优化后:只查需要的字段 + 分页
@Query("SELECT new OrderSummary(o.id, o.orderNo, o.totalAmount, o.status) " +
"FROM Order o WHERE o.userId = :userId")
Page<OrderSummary> findSimpleOrders(@Param("userId") Long userId, Pageable pageable);
// 批量查询,避免N+1
@Query("SELECT p FROM Product p WHERE p.id IN :ids")
List<Product> findByIdIn(@Param("ids") List<Long> ids);
}
优化总结
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
| 优化点 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 数据库查询 | N+1查询,15秒 | 批量查询,150毫秒 | 100倍 |
| 数据传输 | 返回全部字段 | 只返回需要字段 | 减少80% |
| 缓存 | 无缓存 | Redis缓存 | 热数据0毫秒 |
| 分页 | 无分页 | 分页查询 | 内存减少90% |
| 异步 | 同步处理 | 异步刷新 | 响应时间0毫秒 |
九、性能优化检查清单
每次上线前,检查这10项:
-
✅ SQL是否有索引?
执行时间超过100ms的SQL都要看 -
✅ 是否避免N+1查询?
用JOIN FETCH或批量查询 -
✅ 是否合理分页?
列表接口必须分页,默认每页20条 -
✅ 是否加了缓存?
频繁查询的数据加Redis缓存 -
✅ 缓存是否安全?
防雪崩、穿透、击穿 -
✅ 线程池配置是否合理?
核心线程数 = CPU核心数 -
✅ 异步任务是否处理?
发邮件、发短信等走异步 -
✅ JVM参数是否调优?
堆内存、GC算法设置 -
✅ HTTP是否压缩?
启用Gzip压缩 -
✅ 慢查询是否有监控?
监控接口响应时间,超过1秒告警
十、今日思考题
场景:你的电商系统有一个"猜你喜欢"功能,需要:
- 根据用户历史行为推荐商品
- 每次推荐100个商品
- 响应时间要求在200毫秒内
- 每天有1000万次请求
问题:
- 你会如何设计这个系统?
- 数据库、缓存、算法如何配合?
- 如何保证高性能和高可用?
在评论区分享你的架构设计,点赞最高的送《高性能MySQL》+《Redis设计与实现》纸质书!
明天预告:《SpringBoot安全防护:防止你的应用被黑客"光顾"》—— 从认证授权到防攻击,一次性讲清楚!
性能优化工具包:关注公众号回复"性能优化",获取SQL优化脚本、JVM调优模板、性能测试工具!
掘金小贴士:
💡 互动设计:
- 话题讨论:你做过最成功的性能优化是什么?
- 投票:你们系统最慢的接口响应时间是多少?
- 经验征集:分享你的调优经验,抽3位送《Java性能权威指南》
🎁 粉丝福利:
- 关注后回复"调优工具",获取性能监控工具包
- 转发到3个技术群,截图领《高并发系统设计》纸质书
- 评论区抽奖:送5个《阿里Java开发手册》实体版
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目 资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。