You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 14:54:06