avatar

Ryan's Blog

The first step is always the hardest.

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于
  • 工具
Home MySQL Connector/J 负载均衡与故障转移实践
文章

MySQL Connector/J 负载均衡与故障转移实践

Posted 2023-04-27 Updated 8 days ago
By Ryan Chen
35~46 min read

MySQL Connector/J 负载均衡与故障转移实践

前言

在 Java 应用中连接 MySQL,最常见的方式是使用 MySQL Connector/J。除了普通单主机连接外,Connector/J 还支持多主机连接模式,用于实现一定程度的故障转移、负载均衡和读写分离。

不过需要先明确一点:

Connector/J 的多主机能力不是完整的数据库高可用方案,它只能解决“客户端连接如何选择或切换 MySQL 主机”的一部分问题。

真正的生产高可用,还需要结合 MySQL 复制、MGR / InnoDB Cluster、MySQL Router、ProxySQL、负载均衡器、连接池、应用重试和监控告警一起设计。

本文重点整理 Connector/J 中常见的三类多主机连接模式:

  1. Failover:故障转移;
  2. LoadBalance:负载均衡;
  3. Source/Replica Replication:读写分离;
  4. DNS SRV:通过 DNS 服务发现主机。

同时会说明一些容易踩坑的点,例如 autoReconnect 不推荐、事务失败不能无脑重试、synchronized 式的“自动切换”不等于业务无感。

Connector/J 多主机模式概览

Connector/J 常见 JDBC URL 如下。

模式 JDBC URL 示例 典型用途
普通连接 jdbc:mysql://host:3306/db 单实例或代理入口
Failover jdbc:mysql://host1:3306,host2:3306/db 主机不可用时切换
LoadBalance jdbc:mysql:loadbalance://host1:3306,host2:3306/db 多个可写节点之间分摊连接
Replication jdbc:mysql:replication://source:3306,replica1:3306/db Source / Replica 读写分离
DNS SRV jdbc:mysql+srv://service.example.com/db 通过 DNS SRV 发现主机

实践中需要根据数据库拓扑选择模式:

  • 单主多从:更适合 Replication 或使用代理层;
  • 多主可写:可以考虑 LoadBalance,但要确认写入冲突和一致性模型;
  • 主备高可用:可以考虑 Failover,但仍需应用处理异常;
  • Kubernetes / 动态主机发现:可以评估 DNS SRV 或代理入口。

Failover:主机故障后的连接切换

Failover URL 的基本形式:

jdbc:mysql://source-host:3306,backup-host-1:3306,backup-host-2:3306/app_db

常见参数示例:

jdbc:mysql://source-host:3306,backup-host:3306/app_db\
?failOverReadOnly=true\
&secondsBeforeRetrySource=30\
&queriesBeforeRetrySource=50\
&retriesAllDown=120\
&connectTimeout=3000\
&socketTimeout=10000

Failover 的基本思想是:当当前连接主机不可用时,驱动尝试切换到底层列表中的其他主机。

但要注意:

Failover 不是业务完全无感。连接切换通常发生在出现连接异常之后,应用仍然需要处理 SQLException,并在合适的事务边界进行重试。

failOverReadOnly

failOverReadOnly 控制故障转移到非 Source 主机后,连接是否设置为只读。

failOverReadOnly=true

如果备机或 Replica 不应该承载写请求,建议保持只读,避免故障期间误写入错误节点。

需要注意:只读是连接层语义,并不等于数据库权限控制。生产环境仍应在数据库账号、拓扑和代理层面限制写入路径。

secondsBeforeRetrySource

secondsBeforeRetrySource 表示故障转移后,多久尝试重新回到 Source 主机。

secondsBeforeRetrySource=30

适合在 Source 临时不可用时,让连接在一段时间后尝试恢复到原 Source。

queriesBeforeRetrySource

queriesBeforeRetrySource 表示故障转移后,执行多少次查询后尝试回到 Source。

queriesBeforeRetrySource=50

它和 secondsBeforeRetrySource 可以配合使用。一个按时间,一个按查询次数控制回切尝试。

retriesAllDown

retriesAllDown 表示当所有主机都不可用时,驱动的重试次数。

retriesAllDown=120

这个参数只能帮助连接层继续尝试,并不能保证业务请求成功。超过重试限制后,应用仍然会收到异常。

为什么不推荐 autoReconnect?

很多旧文章会建议:

autoReconnect=true

但生产环境不建议依赖 autoReconnect 作为高可用方案。

原因是连接断开后,重连出来的是一个新的物理连接,原连接上的会话状态可能已经丢失,例如:

  • 临时表;
  • session variables;
  • user variables;
  • 当前事务;
  • prepared statement 状态;
  • lock 状态;
  • 当前数据库或字符集等 session 级状态。

因此更推荐的方式是:

  1. 连接池负责检测失效连接并剔除;
  2. 应用捕获 SQLException;
  3. 在明确的事务边界整体重试;
  4. 幂等性由业务层保证;
  5. 不假设某条 SQL 可以被驱动安全自动重放。

简单说:

autoReconnect 解决的是“尝试重新连上”,不是“保证业务语义正确”。

LoadBalance:多主机分摊连接

LoadBalance URL 的基本形式:

jdbc:mysql:loadbalance://mysql-1:3306,mysql-2:3306,mysql-3:3306/app_db

示例:

jdbc:mysql:loadbalance://mysql-1:3306,mysql-2:3306,mysql-3:3306/app_db\
?ha.loadBalanceStrategy=random\
&loadBalanceConnectionGroup=app-cluster\
&ha.enableJMX=true\
&loadBalanceHostRemovalGracePeriod=300000\
&connectTimeout=3000\
&socketTimeout=10000

LoadBalance 适用于多个主机都可以承载同类请求的场景。例如多主写入、多个等价只读节点、或者上层已经保证数据一致性的拓扑。

如果是普通 MySQL 主从复制架构,不建议简单把 Source 和 Replica 放进 loadbalance:// 里承载所有请求,否则写请求可能打到 Replica。

ha.loadBalanceStrategy

当前 Connector/J 文档推荐使用 ha.loadBalanceStrategy 指定负载均衡策略。

常见策略包括:

策略 说明 适用场景
random 随机选择主机 默认通用场景
sequential 按顺序选择 希望稳定优先级
bestResponseTime 根据响应时间选择 追求低延迟
serverAffinity 优先固定主机 希望连接尽量落在指定主机

示例:

ha.loadBalanceStrategy=bestResponseTime

旧资料中常见的 roundRobinLoadBalance 容易和新版本参数体系混淆,建议新文章和新配置优先参考当前官方文档。

loadBalanceConnectionGroup 与 ha.enableJMX

如果需要通过 JMX 管理负载均衡连接,可以配置连接组:

loadBalanceConnectionGroup=app-cluster
ha.enableJMX=true

这样可以在运行时观察和管理某些负载均衡连接行为。

loadBalanceHostRemovalGracePeriod

loadBalanceHostRemovalGracePeriod 用于控制主机从负载均衡列表中移除前的宽限时间。

需要注意单位是毫秒。

loadBalanceHostRemovalGracePeriod=300000

上面表示约 5 分钟。不要误写成:

loadBalanceHostRemovalGracePeriod=300

后者只是 300 毫秒,并不是 300 秒。

事务边界与连接切换

负载均衡连接并不意味着一笔事务中的每条 SQL 都可以随意切到不同主机。

生产中需要注意:

  • 一个事务应绑定到同一个物理连接;
  • 事务中连接失败后,应整体回滚并重新执行业务流程;
  • 不要在事务中依赖驱动对单条 SQL 做自动重试;
  • 非幂等写操作必须由业务层保证重试安全。

Source/Replica Replication:读写分离

Replication URL 的基本形式:

jdbc:mysql:replication://source-host:3306,replica-1:3306,replica-2:3306/app_db

示例:

jdbc:mysql:replication://source-host:3306,replica-1:3306,replica-2:3306/app_db\
?readFromSourceWhenNoReplicas=true\
&connectTimeout=3000\
&socketTimeout=10000

Connector/J 的 Replication 模式通常通过连接的只读状态选择 Source 或 Replica:

Connection connection = dataSource.getConnection();

// 写操作:使用 Source
connection.setReadOnly(false);
try (PreparedStatement ps = connection.prepareStatement(
        "insert into user(name) values (?)")) {
    ps.setString(1, "Alice");
    ps.executeUpdate();
}

// 读操作:可以使用 Replica
connection.setReadOnly(true);
try (PreparedStatement ps = connection.prepareStatement(
        "select * from user where name = ?")) {
    ps.setString(1, "Alice");
    ps.executeQuery();
}

readFromSourceWhenNoReplicas

旧文档和旧资料中常见 readFromMasterWhenNoSlaves。现在 MySQL 官方文档已经使用 Source / Replica 术语,建议改用:

readFromSourceWhenNoReplicas=true

含义是:当没有可用 Replica 时,读请求是否回退到 Source。

这个参数要谨慎使用:

  • 设置为 true:可用性更高,但 Source 压力可能上升;
  • 设置为 false:避免 Source 被读流量压垮,但 Replica 不可用时读请求会失败。

读写分离的一致性风险

读写分离最大的风险不是 JDBC URL,而是复制延迟。

例如:

写入 Source 成功
  ↓
马上从 Replica 查询
  ↓
Replica 尚未追上复制进度
  ↓
读不到刚写入的数据

因此,如果业务需要“写后立刻读到”,要考虑:

  • 写后读强制走 Source;
  • 根据事务上下文选择连接;
  • 使用中间件或框架做读写路由;
  • 监控复制延迟;
  • 对关键读请求避免走 Replica。

DNS SRV:通过 DNS 发现主机

Connector/J 支持 DNS SRV 方式发现 MySQL 主机,例如:

jdbc:mysql+srv://_mysql._tcp.example.com/app_db

DNS SRV 适合主机列表可能变化的环境,但也要注意:

  • DNS 缓存时间;
  • JVM DNS 缓存策略;
  • 域名解析故障;
  • 与连接池长连接的配合;
  • 是否符合当前基础设施规范。

在 Kubernetes 或云环境中,如果已经有稳定的 Service、MySQL Router 或 ProxySQL,通常也可以直接连接代理入口。

与连接池结合使用

大多数 Java 应用不会直接裸用 DriverManager,而是通过连接池,例如 HikariCP。

示例配置:

spring.datasource.hikari.jdbc-url=jdbc:mysql://source-host:3306,backup-host:3306/app_db?failOverReadOnly=true&connectTimeout=3000&socketTimeout=10000
spring.datasource.hikari.username=app_user
spring.datasource.hikari.password=******
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=3000
spring.datasource.hikari.validation-timeout=1000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.keepalive-time=300000

建议:

  1. 不要依赖 autoReconnect=true;
  2. 让连接池负责检测和回收失效连接;
  3. 设置合理的 connectionTimeout 和 validationTimeout;
  4. maxLifetime 不要超过数据库或网络设备的连接回收时间;
  5. 对事务失败做业务层整体重试;
  6. 给关键异常打日志,方便判断是连接故障、主机切换还是数据库异常。

异常处理与重试建议

数据库连接高可用的关键不只是“能不能重连”,而是“失败后业务语义是否正确”。

建议按以下原则处理:

1. 只在事务边界重试

不要在事务中间对某条 SQL 盲目重试。正确做法通常是:

开始事务
  ↓
执行业务 SQL
  ↓
提交失败或连接异常
  ↓
回滚 / 丢弃连接
  ↓
重新开始整个业务流程

2. 只重试幂等操作

查询通常可以重试,但写操作要特别谨慎。

对于写操作,应该通过业务唯一键、请求号、幂等表、去重逻辑等方式保证重复执行不会造成脏数据。

3. 区分连接异常和业务异常

连接异常、死锁、唯一键冲突、SQL 语法错误,不应该使用同一套重试策略。

常见连接类异常通常会体现在 SQLState、异常类型或底层通信异常中。建议结合连接池、日志和监控统一分类。

生产环境建议

1. 优先评估代理层方案

如果目标是生产级高可用,建议优先评估:

  • MySQL Router;
  • ProxySQL;
  • 云数据库代理;
  • Kubernetes Service;
  • SLB / LVS / HAProxy。

代理层可以把拓扑变化、主从切换、健康检查、读写路由等能力从应用中剥离出来,应用侧只连接一个稳定入口。

2. 应用仍然必须处理 SQLException

不管使用 Connector/J Failover、LoadBalance 还是代理层,应用都不能假设数据库故障完全无感。

网络抖动、事务提交中断、连接池旧连接、主从切换窗口,都可能让业务看到异常。

3. 写请求不要透明切到 Replica

如果 Replica 没有写权限,写请求会失败;如果 Replica 被错误开放写权限,风险更大,可能导致数据分叉。

写路径应明确指向 Source 或可写主节点。

4. 读写分离要监控复制延迟

读写分离不是简单地把 select 全部丢给 Replica。

需要关注:

  • 复制延迟;
  • 写后读一致性;
  • 大事务造成的延迟;
  • Replica 故障后的流量回退策略。

5. 配置要区分驱动层和连接池层

Connector/J 参数控制驱动行为;HikariCP 等连接池参数控制连接生命周期、校验、池大小和超时。

两者解决的问题不同,不能混为一谈。

常见配置示例

Failover 示例

jdbc:mysql://mysql-source:3306,mysql-backup:3306/app_db\
?failOverReadOnly=true\
&secondsBeforeRetrySource=30\
&queriesBeforeRetrySource=50\
&retriesAllDown=120\
&connectTimeout=3000\
&socketTimeout=10000

LoadBalance 示例

jdbc:mysql:loadbalance://mysql-1:3306,mysql-2:3306,mysql-3:3306/app_db\
?ha.loadBalanceStrategy=random\
&loadBalanceConnectionGroup=app-cluster\
&ha.enableJMX=true\
&loadBalanceHostRemovalGracePeriod=300000

Source/Replica 示例

jdbc:mysql:replication://mysql-source:3306,mysql-replica-1:3306,mysql-replica-2:3306/app_db\
?readFromSourceWhenNoReplicas=true\
&connectTimeout=3000\
&socketTimeout=10000

不推荐示例

jdbc:mysql://mysql-source:3306/app_db?autoReconnect=true

不推荐把 autoReconnect=true 当成生产高可用方案。它可能掩盖连接异常,并带来 session state 丢失和事务语义不确定的问题。

总结

MySQL Connector/J 提供了多主机连接能力,但它解决的是“客户端连接如何选择和切换主机”的问题,而不是完整的数据库高可用问题。

实践建议:

  • Failover 适合主机故障后的连接切换,但应用必须处理异常;
  • LoadBalance 适合多个等价节点分摊请求,不适合随意混放 Source 和 Replica;
  • Replication 模式可以做读写分离,但要关注复制延迟和写后读一致性;
  • autoReconnect 不推荐作为生产高可用方案;
  • 连接池负责连接生命周期,业务层负责事务边界和幂等重试;
  • 生产环境优先评估 MySQL Router、ProxySQL 或云数据库代理等统一入口。

一句话总结:

Connector/J 可以帮助应用连接多个 MySQL 主机,但真正可靠的高可用,需要驱动、连接池、数据库拓扑、代理层和业务重试共同配合。

参考资料

  • MySQL Connector/J 官方文档:Failover 配置
  • MySQL Connector/J 官方文档:管理负载均衡连接
  • MySQL Connector/J 官方文档:高可用与集群参数
  • MySQL Connector/J 官方文档:JDBC URL 格式
  • MySQL Connector/J 官方文档:Source/Replica Replication 连接
  • MySQL Connector/J 官方文档:DNS SRV
指南
MySQL Connector/J JDBC LoadBalance Failover 数据库高可用 读写分离 连接池
License:  CC BY 4.0
Share

Further Reading

Jun 27, 2026

Agent 架构设计原则:Router、Runtime 与 Business Script 的职责划分

本文整理一套适合 Router Agent + Skill + Runtime 架构的设计原则:Agent 只负责业务决策,Runtime 统一负责执行、恢复、Trace、Checkpoint 和 Evidence,Business Script 只做确定性业务执行。

Sep 9, 2024

Redis 核心概念、数据结构与高可用架构详解

系统梳理 Redis 的核心数据类型、底层结构、过期与淘汰、持久化、事务、主从复制、Sentinel、Cluster、缓存一致性和分布式锁等机制,适合作为 Redis 学习、面试复习和高可用架构设计参考。

Sep 5, 2024

B+树原理与 MySQL InnoDB 索引机制解析

本文从 B+ 树的多叉平衡结构、叶子节点链表、范围查询和磁盘 I/O 特性出发,解释数据库索引为什么常采用 B+ 树,并结合 MySQL InnoDB 的聚簇索引、二级索引、回表、覆盖索引和联合索引机制理解其实际应用。

OLDER

Java List 核心数据结构解析:ArrayList、LinkedList 与线程安全

NEWER

数组基础详解:概念、存储结构与常用操作

Recently Updated

  • Agent 架构设计原则:Router、Runtime 与 Business Script 的职责划分
  • RocketMQ 架构设计与应用最佳实践:高可用消息队列核心解析
  • Redis 核心概念、数据结构与高可用架构详解
  • B+树原理与 MySQL InnoDB 索引机制解析
  • MySQL AUTO_INCREMENT 插入 0 变成自增值的原因与解决方案

Trending Tags

RocketMQ Windows Feign Docker Zipkin SonarQube OkHttp HttpClient API 性能优化

Contents

©2026 Ryan's Blog. Some rights reserved. · 粤ICP备2022031588号