一聚教程网:一个值得你收藏的教程网站

热门教程

Java如何设计在catch块中动态切换主备数据库的逻辑

时间:2026-06-30 09:18:51 编辑:袖梨 来源:一聚教程网

在Java中,主库异常时不应在catch块中动态切备库,因事务已中断、连接不可靠、一致性难保障且违反分层职责;应通过AbstractRoutingDataSource前置路由,由重试机制自动切换读请求,写操作必须失败告警而非切库。

在 Java 应用中,当主数据库异常时,在 catch 块中“动态切换到备库”不是推荐做法,原因在于:事务已中断、连接状态不可靠、数据一致性难保障、且违反分层职责(异常处理不应承担路由逻辑)。真正可行的方式是将主备切换前置到数据访问层,由统一的数据库路由机制完成,catch 块只负责兜底或告警。

用 AbstractRoutingDataSource 实现运行时库路由

Spring 提供的 AbstractRoutingDataSource 是标准解法。它不直接持有连接,而是根据上下文(如线程变量)动态决定使用哪个目标数据源。

  • 定义主、备两个 DataSource Bean(如 HikariCP 实例)
  • 继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey(),返回当前应选的 key(如 "master""slave"
  • 通过 TransactionSynchronizationManager.isCurrentTransactionReadOnly() 或自定义 ThreadLocal<String> 控制路由策略
  • 在 service 层调用前,显式设置路由键:DataSourceContextHolder.set("slave")

异常发生后触发降级路由(非在 catch 中硬切)

不要在 DAO 的 catch 块里写 set("slave") 并重试——这会破坏事务边界,且无法保证备库有最新数据。正确方式是:

  • 将一次数据库操作封装为可重试单元(如用 @Retryable + 自定义 RetryPolicy
  • 首次执行失败后,由重试拦截器自动切换路由键并重试(例如:第一次用 "master",第二次用 "backup"
  • 重试次数限制为 1~2 次,避免雪崩;同时记录日志并触发告警(如 Prometheus + AlertManager)

备库仅用于读操作,写操作必须失败告警

主备架构中,“写切换到备库”本质是故障转移(failover),需 DBA 人工介入或依赖高可用组件(如 MHA、Orchestrator、MySQL Group Replication)。Java 层不能擅自把写请求发给备库,否则会导致:

立即学习“Java免费学习笔记(深入)”;

  • 主从延迟导致重复写、脏写
  • 备库只读模式下抛出 SQLException(如 MySQL 的 ERROR 1290 (HY000)
  • 事务丢失、幂等性失效

因此,写操作捕获异常后应直接返回错误码(如 503 Service Unavailable),而非尝试切库重试。

轻量级兜底:本地缓存 + 异步补偿

若业务允许短暂最终一致,可在主库不可用时启用降级策略:

  • 读操作:查本地 Caffeine 缓存,命中则返回;未命中则返回默认值或空结果,并异步记录补偿任务
  • 写操作:落 Kafka / RocketMQ 消息,由独立消费者服务择机重放至主库
  • 所有降级行为需开关控制(如 Apollo/Nacos 配置),支持运行时开启/关闭

热门栏目