升级到 Spring Boot 3.5,我们的云成本减少了 45%

360影视 欧美动漫 2025-05-30 14:51 2

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

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

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

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

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

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

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

结果令人惊讶:

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

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 发布了,带来了数据库连接管理和 ORM 性能的多项改进。发布说明中提到"资源利用率显著提升",这引起了我的注意。

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

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

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

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

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

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

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

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

# 之前spring.JPA.properties.Hibernate.connection.provider_disables_autocommit=true# 之后spring.jpa.properties.hibernate.connection.provider_disables_autocommit=truespring.jpa.properties.hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_transactionspring.transaction.default-timeout=30

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

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

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

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

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

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

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

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

@Servicepublic 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/

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

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

AWS 成本立竿见影:

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

节省明细:

要真正理解这些变更为何如此有效,必须了解 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=truespring.jpa.properties.hibernate.default_batch_fetch_size=30

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

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

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

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

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

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

代码优化

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

我们重构了复杂的 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 的新查询优化器下,简化后的方法性能更好,框架能做出更优的抓取决策。

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

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

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

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

@Async("taskExecutor")public CompletableFuture processInventoryUpdates(List updates) { // 处理逻辑 return CompletableFuture.completedFuture(null);}@Configurationpublic class AsyncConfig { @Bean public Executor taskExecutor { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor; executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); return executor; }}

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

总结与最佳实践(最大 DB 连接数 - 预留连接数) / 应用实例数

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

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

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

开启详细连接监控:

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

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

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

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

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

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

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

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

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

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

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

我们实现 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/

来源:码农看看

相关推荐