Spring Boot如何配置双数据库,单库故障时自动动态切换至可用库
Spring Boot双活数据库自动故障切换实现方案
核心逻辑
我们通过Spring自带的AbstractRoutingDataSource实现动态数据源路由,配合异常捕获机制和后台健康检查,实现主库连接失败时自动切到备库,主库恢复后可自主切回。
实现步骤
1. 引入基础依赖
以Maven为例,需要引入JDBC启动器和连接池(这里用Druid做演示,也可以用默认的HikariCP):
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency> <!-- 切面能力依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies>
2. 配置双数据源参数
在application.yml中配置主、备两个数据库的连接信息:
spring: datasource: # 主库配置 master: jdbc-url: jdbc:mysql://master-db:3306/your_db?useUnicode=true&characterEncoding=utf8 username: root password: your_pass driver-class-name: com.mysql.cj.jdbc.Driver # 备库配置 backup: jdbc-url: jdbc:mysql://backup-db:3306/your_db?useUnicode=true&characterEncoding=utf8 username: root password: your_pass driver-class-name: com.mysql.cj.jdbc.Driver
3. 实现数据源上下文持有工具
用ThreadLocal存储当前线程使用的数据源标识,避免多线程干扰:
public class DataSourceContextHolder { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static final String MASTER = "master"; public static final String BACKUP = "backup"; public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } public static String getDataSourceKey() { // 默认返回主库 return CONTEXT_HOLDER.get() == null ? MASTER : CONTEXT_HOLDER.get(); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } }
4. 实现动态路由数据源
继承AbstractRoutingDataSource,重写查找数据源key的方法:
public class DynamicRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceKey(); } }
5. 配置动态数据源Bean
将两个数据源注册到路由容器中,设置默认数据源为主库:
@Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.backup") public DataSource backupDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource(DataSource masterDataSource, DataSource backupDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceContextHolder.MASTER, masterDataSource); targetDataSources.put(DataSourceContextHolder.BACKUP, backupDataSource); DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource(); routingDataSource.setTargetDataSources(targetDataSources); routingDataSource.setDefaultTargetDataSource(masterDataSource); return routingDataSource; } // 若使用JdbcTemplate,配置其使用动态数据源 @Bean public JdbcTemplate jdbcTemplate(DataSource dynamicDataSource) { return new JdbcTemplate(dynamicDataSource); } }
6. 实现故障自动切换逻辑
通过AOP捕获数据库连接异常触发切换,同时可加定时健康检查判断主库是否恢复:
异常捕获切换切面
@Aspect @Component public class DataSourceFailoverAspect { // 配置需要拦截的DAO层/Mapper包路径 @Pointcut("execution(* com.yourpackage.mapper.*.*(..))") public void dataSourceOperationPointcut() {} // 捕获数据库连接相关异常 @AfterThrowing(pointcut = "dataSourceOperationPointcut()", throwing = "ex") public void handleDbConnectionFailure(Exception ex) { if (ex instanceof CannotGetJdbcConnectionException || ex.getCause() instanceof SQLException) { // 当前用主库则切到备库 if (DataSourceContextHolder.MASTER.equals(DataSourceContextHolder.getDataSourceKey())) { DataSourceContextHolder.setDataSourceKey(DataSourceContextHolder.BACKUP); // 可自行添加切换日志上报逻辑 } } } }
可选:定时健康检查切回主库
@Component public class DataSourceHealthChecker { @Autowired private DataSource masterDataSource; // 每30秒检查一次主库状态 @Scheduled(fixedDelay = 30000) public void checkMasterHealth() { try (Connection conn = masterDataSource.getConnection()) { // 主库恢复则切回主库 if (DataSourceContextHolder.BACKUP.equals(DataSourceContextHolder.getDataSourceKey())) { DataSourceContextHolder.setDataSourceKey(DataSourceContextHolder.MASTER); } } catch (SQLException e) { // 主库不可用,保持当前配置 } } }
注意事项
- 双库必须提前做好实时数据同步,确保切换后数据一致,推荐用MySQL主从复制、MGR集群等成熟方案保障同步可靠性
- 单次请求触发切换时,当前正在执行的操作会失败,建议配合接口重试机制处理这类异常,提升用户体验
- 建议增加切换阈值,比如连续3次检测到主库连接失败才触发切换,避免网络抖动导致频繁来回切换
- 多实例部署场景下,每个实例独立做健康检测和切换,不需要全局同步切换状态,避免单点判断错误影响所有服务
内容的提问来源于stack exchange,提问作者Anurag Ghosh
相关产品推荐
相关产品推荐

