别把 Galera 当普通主从!3 节点 MySQL 多主集群部署与故障恢复实战
凌晨两点,数据库主节点突然掉线,应用连接池开始疯狂报错,监控群里一排红色告警。
如果用的是传统 MySQL 主从架构,这时候通常要判断从库延迟、确认数据位点、提升从库、修改连接地址。动作看起来不多,真到了生产现场,手一抖就可能把“数据库故障”升级成“数据故障”。
我碰到过类似情况,主库已经不可用,从库还差几十秒日志没追完,业务又催着恢复。切还是不切,很难受。
后来有些业务开始使用 Galera Cluster。它允许多个数据库节点组成一个集群,任意节点理论上都能接受读写,一个节点宕机后,其他节点还能继续提供服务。不过这东西也不是装完就高枕无忧,节点数量、仲裁、网络延迟、写冲突、SST 全量同步,里面全是坑。
这篇文章不只讲怎么把集群搭起来,还会把端口、配置、状态检查、节点扩容、故障恢复和生产避坑一起说清楚。命令可以直接参考,但生产环境请先在测试环境完整演练一遍。
Galera Cluster 到底解决了什么问题
传统 MySQL 主从复制,大致是主库写入 binlog,从库再把日志拉过去执行。它很成熟,使用范围也广,不过通常只能在主库写入,故障切换还要依赖 MHA、Orchestrator、ProxySQL 或其他高可用组件。
Galera Cluster 的思路不太一样。
客户端在某个节点提交事务后,事务会被封装成 Write Set,发送到集群其他节点进行认证。只有通过认证,事务才会提交。其他节点虽然不一定已经把这笔事务完全落到数据页,但至少已经接收并确认了事务。
它常被叫作“同步多主复制”,更准确一点的说法是“虚拟同步复制”。
这里有几个关键词需要记住:
- 多主:集群中的多个节点理论上都可以接收写请求。
- 认证:事务提交前会进行冲突检测。
- 仲裁:集群必须获得多数节点支持,才能维持 Primary 状态。
- SST:新节点没有完整数据时,需要做全量状态传输。
- IST:节点短暂离线后,只补离线期间缺失的增量事务。
Galera 最大的价值,不是让我可以在三台数据库上随便写,而是某个数据库节点故障后,集群仍然能继续提供服务。
我在线上一般还是采用“单写多读”。三个节点都能写,不代表业务一定要这么干。让一个节点承担主要写流量,另外两个节点承担查询和故障接管,能明显减少写冲突,排查问题也更简单。
多主是一种能力,不是必须把它用到极限。
环境怎么规划,三节点不是随便凑出来的
这里使用三台 Rocky Linux 9 服务器演示,数据库选择 MariaDB 10.11 LTS 和 Galera 4。
| 节点 | 主机名 | IP 地址 | 角色 |
|---|---|---|---|
| node1 | db-galera-01 | 192.168.10.21 | 引导节点、默认写节点 |
| node2 | db-galera-02 | 192.168.10.22 | 读节点、备用写节点 |
| node3 | db-galera-03 | 192.168.10.23 | 读节点、仲裁成员 |
生产环境尽量使用奇数节点,三个节点是最常见的起步配置。
两个节点看起来省机器,实际上很尴尬。两台机器之间网络中断后,双方都无法确认对方是否存活。如果允许两边继续写,就可能产生脑裂;如果坚持多数派仲裁,任何一台故障后,剩下的一台又拿不到多数票。
三节点就好理解了,只要还有任意两个节点互相连通,集群仍然拥有多数票。
如果实在只有两台数据库服务器,可以增加一个 garbd 仲裁节点。它不保存完整业务数据,只参与仲裁。不过我还是更推荐三个完整数据节点,仲裁节点解决的是票数问题,解决不了容量和数据副本不足的问题。
还有个容易被忽略的地方,三台服务器最好位于同一个低延迟网络区域。不要为了“异地容灾”,硬把北京、上海、广州三台数据库拉成一个 Galera 集群。网络每抖一下,事务提交延迟和流控就会跟着抖,业务可能没容灾成功,倒是每天都在体验跨地域同步。
真正的异地容灾,通常要结合异步复制、备份恢复或者数据库厂商提供的跨地域方案。
部署前把这些基础工作做完
三台节点都要保证时间同步、主机名正确、磁盘空间充足。
hostnamectl set-hostname db-galera-01
timedatectl set-timezone Asia/Shanghai
systemctl enable --now chronyd
chronyc tracking另外两个节点分别设置自己的主机名。
如果内部 DNS 暂时不完善,可以先在三台机器的 /etc/hosts 中加入解析:
192.168.10.21 db-galera-01
192.168.10.22 db-galera-02
192.168.10.23 db-galera-03Galera 不只使用 MySQL 的 3306 端口。下面几个端口必须提前放通:
| 端口 | 协议 | 用途 |
|---|---|---|
| 3306 | TCP | 客户端连接 MySQL |
| 4444 | TCP | SST 全量状态传输 |
| 4567 | TCP/UDP | Galera 节点复制通信 |
| 4568 | TCP | IST 增量状态传输 |
使用 firewalld 可以这样配置:
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --permanent --add-port=4444/tcp
firewall-cmd --permanent --add-port=4567/tcp
firewall-cmd --permanent --add-port=4567/udp
firewall-cmd --permanent --add-port=4568/tcp
firewall-cmd --reload
firewall-cmd --list-ports不要一遇到连不上就直接关闭防火墙和 SELinux。测试环境这么干确实省事,到了生产就相当于把门锁拆了,因为钥匙不好配。
网络也不能只测 ping,还要检查节点间对应端口:
nc -zv 192.168.10.22 4567
nc -zv 192.168.10.22 4568
nc -zv 192.168.10.22 4444磁盘方面,数据目录和日志目录建议使用 SSD。SST 会把大量数据传给新节点,空间不足时,新节点加入失败,供体节点还可能被拖慢。部署前至少检查这些内容:
df -h
lsblk
free -h
ip addr安装 MariaDB 和 Galera 组件
建议三个节点使用完全一致的软件源和数据库小版本,不要一台 10.6、一台 10.11,还有一台等着“兼容性创造奇迹”。
使用 MariaDB 官方仓库时,可以安装这些组件:
dnf install -y MariaDB-server MariaDB-client MariaDB-backup galera-4不同 Linux 发行版的软件包名称可能稍有区别,有些系统中叫 mariadb-server、mariadb-backup 和 galera。安装完确认版本:
mariadb --version
rpm -qa | grep -Ei 'mariadb|galera'这时先不要急着在三个节点上同时启动数据库。Galera 第一次创建集群,需要明确选择一个引导节点。如果三台服务器各自启动成独立集群,后面再拼到一起,现场会比较热闹。
配置 Galera,关键参数一个都别漏
在三个节点创建 /etc/my.cnf.d/galera.cnf,公共配置如下:
[mysqld]
bind-address=0.0.0.0
default_storage_engine=InnoDB
binlog_format=ROW
innodb_autoinc_lock_mode=2
wsrep_on=ON
wsrep_provider=/usr/lib64/galera-4/libgalera_smm.so
wsrep_cluster_name=production_galera
wsrep_cluster_address=gcomm://192.168.10.21,192.168.10.22,192.168.10.23
wsrep_sst_method=mariabackup
wsrep_sst_auth=sstuser:请替换为高强度密码
wsrep_slave_threads=8
wsrep_log_conflicts=ON
innodb_flush_log_at_trx_commit=1
sync_binlog=1wsrep_provider 的实际路径要以系统为准,可以先查:
find /usr -name libgalera_smm.so 2>/dev/nullnode1 再加入自己的节点配置:
wsrep_node_name=db-galera-01
wsrep_node_address=192.168.10.21node2 配置为:
wsrep_node_name=db-galera-02
wsrep_node_address=192.168.10.22node3 配置为:
wsrep_node_name=db-galera-03
wsrep_node_address=192.168.10.23binlog_format=ROW 是 Galera 的基本要求。默认存储引擎使用 InnoDB,因为 MyISAM 这类非事务表不适合放进 Galera 集群。
业务表最好都有主键。这不是为了让表结构看起来漂亮,而是行复制、冲突检测和删除操作都需要准确定位数据。没有主键的表,在同步和性能方面很容易出问题。
wsrep_slave_threads 是并行应用事务的线程数,不要见到 CPU 有 32 核就直接填 32。可以从 4 或 8 开始,根据接收队列、CPU 使用率和业务写入量调整。新版本中如果提供 wsrep_applier_threads,应优先按照对应版本文档使用新参数。
配置写完先检查文件,密码权限也要收紧:
chmod 600 /etc/my.cnf.d/galera.cnf
mariadbd --verbose --help >/dev/null创建第一个集群节点
只在 node1 执行:
galera_new_cluster随后查看服务状态:
systemctl status mariadb
journalctl -u mariadb -n 100 --no-pager能够登录后,在 node1 创建 SST 账号:
CREATE USER 'sstuser'@'localhost'
IDENTIFIED BY '请替换为高强度密码';
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT
ON *.* TO 'sstuser'@'localhost';
FLUSH PRIVILEGES;不同 MariaDB 和 mariabackup 版本对 SST 账号的权限、认证方式可能略有变化,落地时应以当前版本文档为准。这里不要偷懒使用 root 密码,更不要把弱密码提交到代码仓库。
检查 node1 是否已经成为正常的单节点集群:
SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size';
SHOW GLOBAL STATUS LIKE 'wsrep_cluster_status';
SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment';
SHOW GLOBAL STATUS LIKE 'wsrep_ready';预期结果大致是:
wsrep_cluster_size 1
wsrep_cluster_status Primary
wsrep_local_state_comment Synced
wsrep_ready ON看到 Primary、Synced 和 ON,说明这个节点已经可以正常工作。
galera_new_cluster 只用于创建或者恢复集群,不是普通启动命令。集群已经存在时,不要在其他节点再执行一次,否则可能创建出另一个独立集群。
让 node2 和 node3 加入集群
node1 正常后,在 node2 执行:
systemctl enable mariadb
systemctl start mariadb
journalctl -u mariadb -fnode2 会连接 wsrep_cluster_address 中的现有节点。如果本机没有数据,它会通过 SST 获取完整数据副本。数据库有几百 GB 时,这一步不会像复制一个配置文件那么快,网络、磁盘和供体节点都会有明显压力。
node2 同步完成后,再启动 node3:
systemctl enable mariadb
systemctl start mariadb
journalctl -u mariadb -f我习惯一个节点一个节点地加。虽然同时启动看起来更快,但两个节点一起做 SST,供体节点的磁盘和网络可能瞬间被拉满,业务还没上线,集群先开始喘气。
三个节点都加入后执行:
SHOW GLOBAL STATUS
WHERE Variable_name IN (
'wsrep_cluster_size',
'wsrep_cluster_status',
'wsrep_connected',
'wsrep_ready',
'wsrep_local_state_comment'
);正常结果应该包含:
wsrep_cluster_size 3
wsrep_cluster_status Primary
wsrep_connected ON
wsrep_ready ON
wsrep_local_state_comment Synced只看到 wsrep_cluster_size=3 还不够。节点可能加入了成员列表,但尚未完成数据同步,应用此时连过去依然会失败。所以健康检查至少要同时判断:
wsrep_cluster_status = Primary
wsrep_local_state_comment = Synced
wsrep_ready = ON做一次真实的同步验证
在 node1 创建测试库和表:
CREATE DATABASE galera_test;
USE galera_test;
CREATE TABLE order_test (
id BIGINT NOT NULL AUTO_INCREMENT,
order_no VARCHAR(64) NOT NULL,
amount DECIMAL(12,2) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no)
) ENGINE=InnoDB;
INSERT INTO order_test(order_no, amount)
VALUES ('ORDER-20260918-001', 199.00);登录 node2 查询:
SELECT * FROM galera_test.order_test;再从 node3 插入一条数据:
INSERT INTO galera_test.order_test(order_no, amount)
VALUES ('ORDER-20260918-002', 299.00);回到 node1 查询。如果三个节点都能看到两条记录,基本同步链路已经正常。
不过这个测试只能证明“能同步”,不能证明“扛得住生产”。上线前还要进行并发写入、节点重启、网络中断、磁盘压力、SST 和 IST 等测试。
多主可写,为什么我仍然建议单写
Galera 可以在三个节点同时写,但两个事务如果同时修改同一行,最终只有一个能通过认证,另一个可能收到死锁或提交失败。
比如用户在 node1 修改订单状态,定时任务又通过 node2 修改同一张订单,双方都认为自己操作成功在望,认证阶段却会淘汰其中一个。应用如果没有重试机制,用户看到的就是偶发报错。
生产架构通常可以这么安排:
应用写请求 -> ProxySQL/HAProxy -> node1
应用读请求 -> ProxySQL/HAProxy -> node2、node3
node1 故障 -> 自动摘除 -> node2 接管写请求代理层不能只检查 3306 端口是否存活。一个节点即使还能接受 TCP 连接,也可能已经处于 Non-Primary、Donor 或 Joining 状态。
正确的健康检查,应查询节点的 Galera 状态,只把同时满足 Primary + Synced + Ready 的节点放进后端池。
对读一致性要求很高的业务,还要关注因果读取。事务虽然已经在某节点提交,另一个节点可能仍在应用这笔事务。可以按会话启用:
SET SESSION wsrep_sync_wait = 1;它会在读取前等待必要的事务应用完成,一致性提高了,延迟也可能增加。不是所有查询都要打开,订单支付状态、账户余额这类数据可以考虑,统计报表通常没必要。
节点离线以后,SST 和 IST 有什么区别
节点短时间离线后重新加入,Galera 会优先尝试 IST。
IST 只补充缺失事务,速度快,对集群影响也比较小。它依赖供体节点的 gcache 中还保留着离线期间的事务。如果缺失范围已经超出 gcache,节点只能进行 SST。
可以适当增大 gcache:
wsrep_provider_options="gcache.size=4G;gcache.recover=yes"4G 只是示例,真正大小要根据写入速度和允许离线时间估算。
假设集群平均每小时产生 1GB Write Set,希望节点离线三小时仍能做 IST,那么 gcache 至少要覆盖 3GB,还要留出一定余量。业务高峰写入量可能比平均值大很多,别拿平均值把自己算得太乐观。
SST 相当于给新节点搬一套完整数据库。几百 GB 甚至几 TB 数据执行 SST 时,会大量消耗磁盘 I/O、CPU 和网络带宽。我见过有人白天扩容节点,SST 把供体节点磁盘读满,结果扩容没完成,线上查询先超时了。
大数据量集群做 SST,尽量选择业务低峰,并指定合适的供体节点:
wsrep_sst_donor=db-galera-03当然,指定之前要确认 node3 资源确实够用。别因为它叫“只读节点”,就默认它永远很闲。
三台机器都停了,千万别随便引导
单节点故障比较简单,修复后正常执行:
systemctl start mariadb它会尝试通过 IST 或 SST 重新加入。
整个集群都停止时,恢复操作就要小心了。每个节点的数据目录中通常有一个文件:
cat /var/lib/mysql/grastate.dat内容大致如下:
version: 2.1
uuid: 8a4bxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
seqno: 184233
safe_to_bootstrap: 1seqno 表示节点最后保存的事务序号,safe_to_bootstrap: 1 表示该节点被认为可以安全引导。
正常停机时,最后关闭的节点一般会被标记为可引导节点。此时在它上面执行:
galera_new_cluster等它恢复成 Primary 后,其他节点再执行普通启动。
如果三台服务器是突然断电,seqno 可能都是 -1,这时需要在数据库服务停止的前提下,对各节点执行恢复检查:
sudo -u mysql mariadbd \
--wsrep-recover \
--datadir=/var/lib/mysql日志中会出现类似内容:
WSREP: Recovered position: 集群UUID:事务序号比较三个节点的事务序号,选择序号最高、数据最新的节点作为引导节点。确认无误后,再按当前版本支持的方式设置 safe_to_bootstrap 并启动集群。
这一段最忌讳“找一台看着顺眼的机器直接 galera_new_cluster”。如果选择了数据较旧的节点,其他节点可能无法正常加入,更严重时会以旧数据重新建立集群。
还有 SET GLOBAL wsrep_provider_options='pc.bootstrap=YES' 这类命令,只能在确认其他分区节点全部停止、当前节点数据最新时使用。网络分区期间随手执行,约等于主动邀请脑裂进机房喝茶。
生产环境里最常踩的几类坑
不要把 Galera 当备份。
三个节点会同步正确数据,也会同步误删除。有人在 node1 执行 DROP DATABASE,node2 和 node3 不会站出来说“兄弟你冷静一下”,它们会非常敬业地一起删除。
物理备份、逻辑备份、异地备份、恢复演练,一个都不能少。
不要在高峰期执行大事务。
一次更新几百万行,会生成很大的 Write Set,网络、认证和事务应用都会受到影响。大批量变更应该拆成小批次执行,每批处理几百或几千行,并观察流控指标。
不要随便执行大表 DDL。
Galera 默认使用 TOI,也就是把 DDL 按统一顺序复制到所有节点。大表 ALTER TABLE 一旦执行时间过长,整个集群都可能受到影响。上线前要评估锁表时间,必要时结合在线变更工具,但工具和 Galera 的兼容性必须先测试。
不要跨高延迟网络硬组集群。
Galera 的事务提交需要节点通信确认,网络延迟最终会体现在业务响应时间上。丢包和抖动还会带来频繁的流控、节点掉队和状态传输。
不要只监控 MySQL 是否存活。
下面这些指标更值得盯着:
SHOW GLOBAL STATUS LIKE 'wsrep_flow_control_paused';
SHOW GLOBAL STATUS LIKE 'wsrep_local_recv_queue_avg';
SHOW GLOBAL STATUS LIKE 'wsrep_local_send_queue_avg';
SHOW GLOBAL STATUS LIKE 'wsrep_local_cert_failures';
SHOW GLOBAL STATUS LIKE 'wsrep_local_bf_aborts';
SHOW GLOBAL STATUS LIKE 'wsrep_received_bytes';
SHOW GLOBAL STATUS LIKE 'wsrep_replicated_bytes';wsrep_flow_control_paused 持续升高,说明某些节点处理速度跟不上,集群为了等它而主动限速。
wsrep_local_recv_queue_avg 很大,通常说明本地事务应用积压。可能是磁盘慢、CPU 不足、并行应用线程太少,也可能是业务突然写入过猛。
wsrep_local_cert_failures 和 wsrep_local_bf_aborts 持续增加,要检查多节点并发写冲突和热点行。
监控不能只做一个“当前值”。最好同时展示一分钟、五分钟和一小时趋势,否则某个指标从 0.01 涨到 0.3,单看一眼数字很难判断问题到底多严重。
我整理的一份上线检查清单
真正上线之前,我一般会把下面这些内容逐项勾掉:
- 三个节点版本、时区和时间同步状态一致。
- 节点间 4444、4567、4568 端口双向连通。
- 所有业务表使用 InnoDB,核心表都有主键。
- 三个节点均处于
Primary、Synced、Ready状态。 - 应用连接通过代理层接入,没有写死某个数据库 IP。
- 代理健康检查能够识别 Galera 状态,而不只是检查 3306。
- 已完成单节点宕机、单节点重启和写节点切换测试。
- 已验证短时间离线走 IST、长时间离线走 SST。
- 已演练整个集群关闭后的引导恢复。
- 已测试业务遇到认证冲突和死锁时的重试逻辑。
- 已监控流控、接收队列、认证失败和节点状态。
- 已制定 SST 的带宽、磁盘容量和业务低峰执行方案。
- 已配置独立备份,并且真的做过恢复,不是只看到“备份成功”四个字。
- 已保留部署配置、变更记录、故障处理手册和联系人信息。
这个清单看起来有些啰嗦,但数据库高可用最怕“我以为”。
我以为节点能自动恢复,我以为代理会摘除异常节点,我以为备份可以恢复,我以为网络不会同时中断。故障现场里,很多问题就是这四个字凑到了一起。
写在最后
Galera Cluster 的部署命令并不复杂,真正有难度的是生产使用。
把三台 MariaDB 启动起来,只能算完成了安装;知道怎么判断仲裁、怎么控制写冲突、怎么避免大事务、怎么处理 SST,以及整个集群宕机后从哪台机器引导,才算真正会用。
记住一句话:
Galera 提供的是数据库节点高可用,不是自动备份,也不是万能容灾。多主只是能力,稳定才是目的。
建议把文中的端口清单、健康检查条件和集群恢复步骤收藏下来。数据库故障发生时,现场很少有人还有心情重新翻几十页文档,一份经过演练的操作清单,往比临场发挥靠谱得多。
如果这篇文章对你有帮助,欢迎点赞、转发给身边做运维、开发和架构的朋友。也欢迎关注 @运维躬行录,后续我会继续分享数据库高可用、Linux 故障排查、容器运维和生产事故复盘,尽量把每个坑讲明白,让大家少熬几次夜。
公众号:耕云躬行录
个人博客:躬行笔记