运维知识
悠悠
2026年9月18日

别把 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 地址角色
node1db-galera-01192.168.10.21引导节点、默认写节点
node2db-galera-02192.168.10.22读节点、备用写节点
node3db-galera-03192.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-03

Galera 不只使用 MySQL 的 3306 端口。下面几个端口必须提前放通:

端口协议用途
3306TCP客户端连接 MySQL
4444TCPSST 全量状态传输
4567TCP/UDPGalera 节点复制通信
4568TCPIST 增量状态传输

使用 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-servermariadb-backupgalera。安装完确认版本:

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=1

wsrep_provider 的实际路径要以系统为准,可以先查:

find /usr -name libgalera_smm.so 2>/dev/null

node1 再加入自己的节点配置:

wsrep_node_name=db-galera-01
wsrep_node_address=192.168.10.21

node2 配置为:

wsrep_node_name=db-galera-02
wsrep_node_address=192.168.10.22

node3 配置为:

wsrep_node_name=db-galera-03
wsrep_node_address=192.168.10.23

binlog_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

看到 PrimarySyncedON,说明这个节点已经可以正常工作。

galera_new_cluster 只用于创建或者恢复集群,不是普通启动命令。集群已经存在时,不要在其他节点再执行一次,否则可能创建出另一个独立集群。

让 node2 和 node3 加入集群

node1 正常后,在 node2 执行:

systemctl enable mariadb
systemctl start mariadb
journalctl -u mariadb -f

node2 会连接 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-PrimaryDonorJoining 状态。

正确的健康检查,应查询节点的 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: 1

seqno 表示节点最后保存的事务序号,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_failureswsrep_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 故障排查、容器运维和生产事故复盘,尽量把每个坑讲明白,让大家少熬几次夜。

公众号:耕云躬行录

个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码