上周五凌晨,我们的支付对账服务突然开始疯狂打印DataSource is closed的报错。监控大盘显示数据库连接池活跃连接数归零,而流量明明没有任何波动------这场景是不是让你想起某个不眠夜?
现象:自动配置的"智能"成了定时炸弹
问题出现在一个日均处理 200w+ 账务记录的 SpringBoot 2.7 服务。在接收到 Kafka 消息后,服务会通过 JPA 写入 MySQL,核心逻辑简单到只有三行:
java
@Transactional
public void handlePayment(PaymentMessage msg) {
paymentRepository.save(new Payment(msg.getTxId(), msg.getAmount()));
}
但诡异的是,当 Kafka 消费者重启时,偶尔会触发整个连接池不可用。日志里明晃晃的HikariPool-1 - Database connection pool is closed和Cannot get a connection, pool error交替出现,而这时离服务启动已经过去了 15 分钟。
根因:多数据源下的自动配置博弈
经过反复复现和调试,发现问题出在 SpringBoot 的 自动配置竞速条件 上。我们的项目由于历史原因混用了两个数据源:
- 主数据源:通过
@ConfigurationProperties(prefix = "spring.datasource")显式配置 - 监控数据源:在另一个
@Configuration类里手动定义的DataSource
SpringBoot 的自动配置机制在这里玩了个危险游戏:
DataSourceAutoConfiguration看到存在自定义数据源配置后,会跳过主数据源初始化- 但
HikariAutoConfiguration仍然会尝试基于spring.datasource.hikari.*参数构建连接池 - 当监控数据源先完成初始化时,主数据源的 Hikari 池会被误判为"备用池"而悄悄关闭
用代码来说,错误场景是这样的:
java
// 错误配置示例:两个数据源配置互相干扰
@Configuration
public class MonitoringConfig {
@Bean
@ConfigurationProperties("monitoring.datasource")
public DataSource monitoringDataSource() {
return DataSourceBuilder.create().build(); // 这个先初始化了!
}
}
// application.properties
spring.datasource.url=jdbc:mysql://primary-db
spring.datasource.hikari.maximum-pool-size=20
monitoring.datasource.url=jdbc:mysql://monitoring-db
解法:用明确的条件注解划清界限
正确的做法是强制让自动配置按我们设定的路线走:
java
@Configuration
// 关键注解:明确排除自动数据源配置
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class PrimaryDataSourceConfig {
@Primary
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
@Configuration
public class MonitoringConfig {
@Bean
@ConfigurationProperties("monitoring.datasource")
public DataSource monitoringDataSource() {
return DataSourceBuilder.create().build();
}
}
- 性能对比*:改造后,服务启动时间从原来偶尔出现的 15 分钟连接池故障,降低到完全稳定的 30 秒内可用。
避坑清单:多数据源场景下的死亡陷阱
- 隐式依赖陷阱 :当你混合使用
spring-boot-starter-data-jpa和手动数据源时,HibernateJpaAutoConfiguration可能偷偷使用错误的数据源 - 连接池参数失效 :如果没显式指定
type = HikariDataSource.class,连接池参数可能被默认配置覆盖 - 监控指标错乱:多个 Hikari 池的 JMX 注册名称冲突会导致监控数据互相覆盖
- 测试环境假象:用 H2 内存数据库测试时可能掩盖这个问题,因为 H2 的连接管理行为与生产数据库不同
写在最后
SpringBoot 自动配置的本质是一套精巧的"约定优于配置"机制,但当你需要打破这些约定时,必须用显式声明替代隐式魔法。下次看到数据库连接池神秘关闭时,不妨先检查是否存在配置边界模糊的问题------你在多数据源项目里还踩过哪些坑?欢迎分享你的战场故事。