⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 SpringForAll社区 「s4a」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

上个季度,我带着越来越焦虑的心情盯着我们公司的 AWS 账单。尽管服务的客户数量基本持平,但我们的云成本在过去一年里持续攀升。作为负责后端基础设施的技术负责人,我必须在下一个预算评审前找到解决方案。

我没想到的是,一次看似例行的 Spring Boot 升级,加上一些有针对性的配置调整,竟然让我们的 AWS 开支几乎减半。以下是我们如何发现问题、实施变更,并彻底提升应用资源效率的全过程。

成本加剧的危机

我们公司运营着一个 SaaS 平台,帮助中型企业管理多渠道的库存。我们不是巨头(大约 5,000 个客户,每天处理 120,000 个订单—),但 AWS 账单已经涨到了令人头疼的每月 27,000 美元。_

CFO 在季度会议上说:”我们要么削减成本,要么涨价。”但在当前市场,涨价根本不现实。”

挑战很明确:在不影响应用性能和可靠性的前提下,找到显著的基础设施节省空间。但从哪里下手?

找出资源消耗大户

我们的应用架构对现代 Java 项目来说很常见:

  • Spring Boot 2.7 后端服务
  • PostgreSQL RDS 实例做持久化存储
  • Redis 做缓存
  • EC2 实例组成自动伸缩组,挂在负载均衡器后面

第一步是搞清楚钱都花在哪了。我设置了详细的成本分配标签,并用 AWS Cost Explorer 分析了我们的支出模式。

结果令人惊讶:

  • EC2 实例:占 58%
  • RDS PostgreSQL:占 25%
  • 数据传输:占 12%
  • 其他服务(Redis、S3 等):占 5%

EC2 成本成了首要目标,深入分析后发现更有意思的现象:我们运行的实例数量远超实际流量需求。自动伸缩经常被触发,启动的新实例大多处于低利用率。

资源利用率之谜

监控数据显示出一种奇怪的模式。每台 EC2 实例启动时各项指标都很健康,但随后会逐步出现:

  1. CPU 利用率逐步升高(最终达到 70–80%)
  2. JVM 堆内存持续增长
  3. 响应时间变慢
  4. 吞吐量下降

大约 12 小时后,指标恶化到自动伸缩被触发,启动新实例。但这些新实例并没有处理更多流量,只是在弥补已有实例性能下降的问题。

“看起来像是某种资源泄漏。”我对团队说,”但不是典型的内存泄漏,而是应用效率在逐步下降。”

数据库连接的真相

开启详细性能监控和日志分析后,我们发现了惊人的问题:应用创建了过多的数据库连接,且很多连接没有被正确关闭。

典型的 API 请求流程应该是:

Request → Controller → Service → Repository → Database

但实际连接使用却是:

初始请求 → 打开 5 个 DB 连接 → 关闭 3 个连接 → 泄漏 2 个连接

这些泄漏的连接会不断积累,直到连接池耗尽,导致性能下降,最终触发自动伸缩。

罪魁祸首?我们的应用用的是 Spring Data JPA,并有一些自定义的 repository 实现,没有正确管理事务边界和连接生命周期。

Spring Boot 3.5 的启示

就在这时,Spring Boot 3.5 发布了,带来了数据库连接管理和 ORM 性能的多项改进。发布说明中提到”资源利用率显著提升”,这引起了我的注意。

进一步研究后,我发现 Spring Boot 3.5 包含:

  1. 更强的连接池集成
  2. 改进的事务管理
  3. 更智能的资源清理
  4. 更好地处理懒加载场景

升级能解决我们的问题吗?值得一试。

关键配置变更带来的转机

我们决定升级到 Spring Boot 3.5,并针对数据库连接管理做了几项关键配置调整。以下是最有影响力的具体变更:

1. 连接池优化

我们将默认的 HikariCP 配置调整为更适合我们负载的参数:

# 之前
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=10
# 之后
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=120000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.leak-detection-threshold=60000

关键新增项是 leak-detection-threshold,帮助我们识别和记录潜在的连接泄漏。降低 minimum-idle 也避免了在低峰期保留过多空闲连接。

2. 事务管理改进

我们优化了事务管理配置:

# 之前
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
# 之后
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
spring.jpa.properties.hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
spring.transaction.default-timeout=30

DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION 这个设置堪称”神器”,它确保数据库连接在真正需要时才获取,并在事务结束后立即释放。

3. JPA 查询优化

我们做了多项 JPA 和 Hibernate 优化:

# 批量处理提升性能
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
# 查询优化
spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true
spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true
spring.jpa.properties.hibernate.default_batch_fetch_size=30
# Spring Boot 3.5 新增
spring.jpa.properties.hibernate.query.optimizer.enabled=true

Spring Boot 3.5 的新查询优化器,显著减少了常用操作所需的数据库查询次数。

4. 语句缓存

我们开启了预编译语句缓存,对数据库性能提升明显:

spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250
spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=2048
spring.datasource.hikari.data-source-properties.cachePrepStmts=true
spring.datasource.hikari.data-source-properties.useServerPrepStmts=true

这些设置确保常用 SQL 语句被缓存,减少了语句准备的开销。

5. 针对场景的连接管理

对于最消耗资源的接口,我们实现了针对性的事务和连接设置:

@Service
public class InventorySyncService {

@Transactional(timeout = 60)
@QueryHints(@QueryHint(name = "org.hibernate.fetchSize", value = "100"))
public void synchronizeInventory() {
// 资源密集型操作
}
}

这样可以针对不同操作设置不同的事务和抓取行为,而不是一刀切。

实施与即时成效

这些变更需要仔细测试,因为数据库连接问题往往隐蔽且依赖环境。我们的做法:

  1. 搭建与生产一致的预发环境
  2. 升级到 Spring Boot 3.5 并应用新配置
  3. 进行大量压力测试验证变更
  4. 对比前后连接使用模式

初步结果令人振奋:

  • 单实例平均数据库连接数从 7.8 降到 3.2
  • 连接获取时间降低 68%
  • 72 小时压力测试期间未检测到连接泄漏

但真正的考验在生产环境。

最近我们翻译了Spring Boot的中文文档,有需要的小伙伴可以通过以下链接获取:

Spring Boot 3.4中文文档:https://doc.spring4all.com/spring-boot/3.4.6/

Spring Boot 3.5中文文档:https://doc.spring4all.com/spring-boot/3.5.0/

上线与 AWS 成本影响

我们在维护窗口将变更上线,并立即开始监控。实际效果比预发还要明显:

  • EC2 集群平均 CPU 利用率从 62% 降到 28%
  • JVM 垃圾回收暂停减少 76%
  • 单实例吞吐量提升 120%
  • 平均响应时间从 187ms 降到 74ms

最重要的是,自动伸缩事件几乎消失。原本高峰期需要 20–24 台 EC2,现在只需 9–10 台即可稳定运行。

AWS 成本立竿见影:

上线前月账单:$27,000
上线后月账单:$14,850
总节省:$12,150(45%)

节省明细:

  • EC2 成本降低 58%(实例数减少,CPU 利用率下降)
  • RDS 成本降低 32%(连接数减少,查询效率提升)
  • 数据传输成本降低 15%(API 响应更高效)

技术细节解析:为何如此有效

要真正理解这些变更为何如此有效,必须了解 Spring Boot 3.5 的技术改进,以及我们的配置如何充分利用了这些特性。

连接生命周期的改进

Spring Boot 3.5 从根本上改变了数据库连接的管理方式。旧版本往往过早获取连接且持有时间过长。

新设置 DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION 保证:

  1. 只有在即将执行 SQL 时才获取连接
  2. 事务结束后立即释放连接

这样大大缩短了连接持有时间,让更小的连接池也能应对同样的负载。

查询优化引擎

Spring Boot 3.5 的新查询优化器解决了多种常见低效问题:

  1. N+1 查询预防:检测潜在 N+1 查询模式并自动转为高效批量查询
  2. 连接优化:分析实体关系,选择更优的连接策略
  3. 抓取大小调优:根据结果集大小自动调整 JDBC fetch size

我们的配置已启用并调优了该优化器:

spring.jpa.properties.hibernate.query.optimizer.enabled=true
spring.jpa.properties.hibernate.default_batch_fetch_size=30

语句缓存

数据库语句准备开销不容小觑。开启语句缓存后,常用查询可跳过准备阶段:

spring.datasource.hikari.data-source-properties.cachePrepStmts=true
spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250

对于我们这种查询模式较为固定的应用,显著降低了数据库 CPU 占用并提升响应速度。

泄漏检测与预防

泄漏检测配置对定位剩余问题至关重要:

spring.datasource.hikari.leak-detection-threshold=60000

该设置会在连接持有超时后记录详细堆栈,帮助我们定位和修复代码中的连接管理问题。

代码优化

虽然配置变更带来了主要提升,我们也做了几项代码优化作为补充:

1. 简化 Repository 方法

我们重构了复杂的 repository 方法,充分利用 Spring Data JPA 的查询派生能力:

// 之前
@Query("SELECT p FROM Product p LEFT JOIN FETCH p.variants v WHERE p.sku = :sku")
Product findBySku(@Param("sku") String sku);
// 之后
Product findBySku(String sku);

在 Spring Boot 3.5 的新查询优化器下,简化后的方法性能更好,框架能做出更优的抓取决策。

2. 显式事务边界

我们让事务边界更明确,尤其是只读操作:

@Transactional(readOnly = true)
public ProductDTO getProduct(String sku) {
Product product = productRepository.findBySku(sku);
return mapper.toDTO(product);
}

readOnly = true 提示 Spring 和数据库进一步优化查询执行。

3. 批量操作异步处理

对于资源密集型操作,我们采用异步处理并控制资源消耗:

@Async("taskExecutor")
public CompletableFuture<Void> processInventoryUpdates(List<InventoryUpdate> updates) {
// 处理逻辑
return CompletableFuture.completedFuture(null);
}
@Configuration
publicclassAsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
return executor;
}
}

这样保证批量操作不会占用过多数据库连接或 CPU。

总结与最佳实践

在这个过程中,我们总结出几条对其他团队也有参考价值的最佳实践:

1. 连接池大小要与数据库连接数匹配

最大连接池大小应按如下公式计算:

(最大 DB 连接数 - 预留连接数) / 应用实例数

比如 RDS 最大连接 100,5 个应用实例:

(100 - 5 预留) / 5 实例 = 每实例 19 个连接

我们取整到 20,留有余地。

2. 监控并记录连接使用

开启详细连接监控:

spring.datasource.hikari.metrics.registry-type=log
logging.level.com.zaxxer.hikari=DEBUG

这样能清晰看到连接使用模式,便于排查问题。

3. 区分环境配置

不同环境需求不同。我们为不同环境设置了专属 profile:

# 开发环境
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.minimum-idle=1
# 生产环境
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5

这样开发环境不会占用多余资源。

4. 定期审查查询性能

我们实现了 SQL 性能监控,记录慢查询及其执行计划:

spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=250

帮助我们及时发现和优化最耗资源的查询。

意外收获:超越成本的好处

虽然降低 AWS 成本是首要目标,但我们还发现了不少额外好处:

1. 开发体验提升

连接管理更好,错误信息更清晰,开发过程中遇到的超时和连接问题大幅减少。

2. 压测更准确

我们的压力测试结果更可预测、更贴近生产,有助于容量规划。

3. 运维事件减少

变更后六个月内,我们得到了这样的改变:

  • 连接相关告警减少 87%
  • 自动伸缩事件减少 92%
  • 数据库连接相关的生产事故为零

4. 环保效益

服务器数量从 24 台降到 10 台,不仅省钱,也大幅减少了能耗和碳排放。

结语:把配置当作一等优化手段

作为软件工程师,面对性能挑战时我们常常关注代码优化、算法改进和架构调整。我们的经验表明,配置(尤其是数据库连接相关配置)同样值得作为一等优化手段。

Spring Boot 3.5 的改进是基础,而我们精细的配置调优则释放了这些提升的全部潜力。最终收获的不只是成本节省,还有更可靠、高效、环保的应用。

对于面临类似挑战的团队,我的建议是:

  1. 先彻底分析当前资源使用模式
  2. 理解框架的数据库连接行为
  3. 针对性地调整配置
  4. 持续监控并迭代优化

我们实现 45% AWS 账单削减,并不是靠大刀阔斧重构或彻底换架构,而是通过理解和优化已有系统——有时,最有效的提升就藏在你应用的配置文件里。

最近我们翻译了Spring Boot的中文文档,有需要的小伙伴可以通过以下链接获取:

Spring Boot 3.4中文文档:https://doc.spring4all.com/spring-boot/3.4.6/

Spring Boot 3.5中文文档:https://doc.spring4all.com/spring-boot/3.5.0/

文章目录
  1. 1. 成本加剧的危机
  2. 2. 找出资源消耗大户
  3. 3. 资源利用率之谜
  4. 4. 数据库连接的真相
  5. 5. Spring Boot 3.5 的启示
  6. 6. 关键配置变更带来的转机
    1. 6.0.1. 1. 连接池优化
    2. 6.0.2. 2. 事务管理改进
    3. 6.0.3. 3. JPA 查询优化
    4. 6.0.4. 4. 语句缓存
  • 7. 5. 针对场景的连接管理
  • 8. 实施与即时成效
  • 9. 上线与 AWS 成本影响
  • 10. 技术细节解析:为何如此有效
  • 11. 连接生命周期的改进
  • 12. 查询优化引擎
  • 13. 语句缓存
  • 14. 泄漏检测与预防
  • 15. 代码优化
    1. 15.0.1. 1. 简化 Repository 方法
    2. 15.0.2. 2. 显式事务边界
    3. 15.0.3. 3. 批量操作异步处理
  • 16. 总结与最佳实践
    1. 16.0.1. 1. 连接池大小要与数据库连接数匹配
    2. 16.0.2. 2. 监控并记录连接使用
    3. 16.0.3. 3. 区分环境配置
    4. 16.0.4. 4. 定期审查查询性能
  • 17. 意外收获:超越成本的好处
    1. 17.0.1. 1. 开发体验提升
    2. 17.0.2. 2. 压测更准确
    3. 17.0.3. 3. 运维事件减少
    4. 17.0.4. 4. 环保效益
  • 18. 结语:把配置当作一等优化手段