4. 数据库高可用:MySQL主从复制、读写分离、连接池配置、故障自动切换
调度系统里,数据库往往是整个链路中最脆弱的一环。我见过不少调度系统,应用层做得花里胡哨,结果数据库一挂,整个集群直接瘫痪。说白了,数据库的高可用才是调度系统真正的底线。
这一章,我们聚焦 MySQL 的高可用方案。我会从主从复制讲起,逐步深入到读写分离、连接池调优,最后落地到故障自动切换。嗯,这些都是我在生产环境里反复踩过坑之后沉淀下来的东西。
4.1 主从复制:高可用的地基
主从复制是 MySQL 高可用的基础。没有它,后面的一切都是空中楼阁。
MySQL 主从复制的核心机制,说白了就是主库把变更记录到二进制日志(binlog),从库拉取这些日志并重放。整个过程是异步的,所以从库的数据会有一定延迟。
核心流程:
- 主库提交事务,写入 binlog
- 从库的 I/O 线程连接主库,拉取 binlog 并写入 relay log
- 从库的 SQL 线程读取 relay log,重放事务
我个人习惯用 GTID(全局事务标识)模式。相比传统的基于文件名+位置的复制,GTID 让主从切换变得简单很多。你不需要手动去找 binlog 文件名和偏移量,MySQL 自己就能搞定。
配置示例(GTID 模式):
# 主库配置
[mysqld]
server-id = 1
log-bin = mysql-bin
gtid_mode = ON
enforce-gtid-consistency = ON
# 从库配置
[mysqld]
server-id = 2
relay-log = relay-log
gtid_mode = ON
enforce-gtid-consistency = ON
read_only = ON
我在项目中遇到过一个问题:从库延迟太大,导致调度任务读取到过期数据。后来排查发现是从库的 SQL 线程是单线程的,主库写入压力一大,从库就追不上。解决方案是开启并行复制。
# 从库开启并行复制
[mysqld]
slave-parallel-type = LOGICAL_CLOCK
slave-parallel-workers = 4
slave-preserve-commit-order = ON
这里要注意,并行复制不是线程越多越好。我曾经试过开到 16 个线程,结果因为锁冲突反而更慢。一般来说,4-8 个线程是比较合理的范围。
4.2 读写分离:让主库喘口气
调度系统的数据库压力通常是不对称的。写操作集中在任务状态更新、调度记录写入;读操作则集中在任务查询、历史数据检索。如果所有请求都打到主库,主库迟早会扛不住。
读写分离的思路很简单:写请求走主库,读请求走从库。但实现起来有几个坑要注意。
读写分离的常见陷阱:
- 主从延迟导致脏读:任务刚写入主库,立即从从库读取,可能读不到。解决方案是写后读一致性,关键查询强制走主库。
- 事务内读写不一致:同一个事务里,先写后读,如果读走从库,可能读到旧数据。我建议事务内的读也走主库。
- 连接池混乱:主库和从库的连接池要分开管理,不能混用。
我常用的方案是在应用层通过数据源路由来实现读写分离。以 Spring 为例,可以动态切换数据源。
// 读写分离数据源路由示例
public class ReadWriteDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType(); // 返回 'master' 或 'slave'
}
}
// 使用注解标记读操作
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}
// AOP 切面自动切换
@Around("@annotation(readOnly)")
public Object route(ProceedingJoinPoint pjp, ReadOnly readOnly) {
try {
DbContextHolder.setDbType("slave");
return pjp.proceed();
} finally {
DbContextHolder.clear();
}
}
你想想看,如果每次读请求都走主库,那主库的压力根本降不下来。读写分离做得好,主库的 QPS 能下降 60%-70%。
4.3 连接池配置:别让连接成为瓶颈
连接池的配置看似简单,但调优空间很大。我见过太多调度系统因为连接池配置不当,导致数据库连接耗尽或者响应变慢。
常用的连接池有 HikariCP、Druid、Tomcat JDBC Pool。我个人偏好 HikariCP,性能好,配置简洁。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| maximumPoolSize | 10-20 | 不是越大越好,过大反而增加数据库负担 |
| minimumIdle | 5-10 | 保持一定空闲连接,应对突发流量 |
| connectionTimeout | 30000ms | 连接超时,避免长时间等待 |
| idleTimeout | 600000ms | 空闲连接超时,释放资源 |
| maxLifetime | 1800000ms | 连接最大存活时间,防止连接泄漏 |
HikariCP 配置示例:
spring:
datasource:
hikari:
maximum-pool-size: 15
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
pool-name: SchedulePool
connection-test-query: SELECT 1
我曾经犯过一个错误:把 maximumPoolSize 设成了 100,觉得这样并发能力更强。结果数据库的连接数被打满,其他服务连不上数据库,整个系统都受影响。后来我总结了一个经验公式:连接数 = (核心数 * 2) + 有效磁盘数。对于调度系统,10-20 个连接通常就够用了。
4.4 故障自动切换:MHA vs Orchestrator
主从复制和读写分离都做好了,但如果主库挂了,谁来接管?手动切换太慢,而且容易出错。我们需要一个自动故障切换工具。
目前主流的选择有两个:MHA 和 Orchestrator。我两个都用过,说说我的感受。
| 特性 | MHA | Orchestrator |
|---|---|---|
| 架构 | Manager + Node | 单进程 + Raft 集群 |
| 部署复杂度 | 中等 | 简单 |
| 切换速度 | 10-30秒 | 5-15秒 |
| 拓扑管理 | 手动配置 | 自动发现 |
| Web UI | 无 | 有 |
| 社区活跃度 | 低(已停止维护) | 高 |
我个人现在更倾向于 Orchestrator。原因很简单:MHA 已经停止维护了,而 Orchestrator 还在持续更新,而且它的自动拓扑发现功能非常实用。
4.4.1 MHA 的配置要点
虽然 MHA 已经过时,但很多老系统还在用。如果你不得不维护 MHA,有几个关键点要注意。
# MHA 配置文件示例
[server default]
manager_workdir=/var/log/mha
manager_log=/var/log/mha/manager.log
ssh_user=root
repl_user=repl
repl_password=repl_password
ping_interval=3
secondary_check_script=masterha_secondary_check -s 192.168.1.2 -s 192.168.1.3
[server1]
hostname=192.168.1.1
candidate_master=1
[server2]
hostname=192.168.1.2
candidate_master=1
[server3]
hostname=192.168.1.3
no_master=1
MHA 避坑指南:
- SSH 免密登录必须配好,否则切换时会失败
- ping_interval 不要设得太小,否则网络抖动会误触发切换
- candidate_master 要合理设置,不是所有从库都适合升为主库
4.4.2 Orchestrator 的部署与使用
Orchestrator 的部署比 MHA 简单得多。一个二进制文件就能跑起来,而且支持 Raft 集群部署,自身就是高可用的。
# Orchestrator 配置文件核心部分
{
"Topology": {
"Master": "192.168.1.1:3306",
"User": "orchestrator",
"Password": "orchestrator_password"
},
"Recovery": {
"PeriodicCheckInterval": 5,
"FailureDetectionPeriod": 10,
"RecoveryPeriodBlockSeconds": 300
},
"Raft": {
"Enabled": true,
"DataDir": "/var/lib/orchestrator/raft",
"Nodes": [
"192.168.1.10:10008",
"192.168.1.11:10008",
"192.168.1.12:10008"
]
}
}
Orchestrator 的自动切换逻辑很智能。它会检测主库是否真的挂了,然后自动选出最合适的从库升为主库,并重新配置其他从库的复制关系。整个过程不需要人工干预。
Orchestrator 的优势:
- 自动发现 MySQL 拓扑,不需要手动维护节点列表
- 支持故障恢复后的自动重连
- 提供 Web UI,可以直观地查看集群状态
- 支持 HTTP API,方便集成到调度系统的运维平台
4.5 整体架构图
下面这张图展示了调度系统数据库高可用的整体架构。从应用层到数据库层,每一层都有对应的高可用保障措施。
4.6 总结与避坑清单
数据库高可用不是一蹴而就的。从主从复制到读写分离,再到连接池调优和故障自动切换,每一步都需要仔细打磨。
核心要点回顾:
- 主从复制优先使用 GTID 模式,简化切换流程
- 读写分离要注意主从延迟,关键查询强制走主库
- 连接池大小不是越大越好,10-20 个连接通常够用
- 故障切换工具推荐 Orchestrator,部署简单且功能强大
我曾经踩过的坑:
- 连接池设得太大,数据库连接数被打满,其他服务受影响
- MHA 的 SSH 免密没配好,故障时切换失败
- 读写分离没处理事务内一致性,导致业务逻辑出错
- 从库并行复制线程数设得太多,反而因为锁冲突变慢
嗯,数据库高可用这块内容比较多,但都是实打实的生产经验。你把这些配置和方案落地到自己的调度系统里,数据库层面的高可用基本就稳了。