Ubuntu 16.04下MySQL 5.6+Galera高可用集群实战指南
1. 为什么在 Ubuntu 16.04 上坚持用 MySQL 5.6 Galera 是个务实选择你可能已经看到满屏的“MySQL 8.0 高可用方案”“Percona XtraDB Cluster 最新实践”但如果你手头正维护着一批运行在物理服务器或老旧虚拟机上的生产服务操作系统锁死在 Ubuntu 16.04数据库版本被上游中间件或遗留应用强依赖于 MySQL 5.6 —— 那么跳过所有花哨的新技术栈老老实实把 Galera Cluster 在这个组合上跑稳不是妥协而是对系统稳定性的真正尊重。Galera 的核心价值从来不是“最新”而是“确定性”。它用同步复制Synchronous Replication机制在事务提交前就确保所有节点达成一致彻底规避了传统主从异步复制中常见的数据漂移、脑裂、延迟从库不可用等问题。而 MySQL 5.6 mysql-wsrep-5.6 这个组合是 Canonical 官方仓库为 Ubuntu 16.04 提供的、经过长期验证的稳定分支。它不像社区版 MySQL 5.6 那样缺失 wsrep 接口也不像后期打包的 5.7 版本那样在旧内核上频繁触发内存映射异常。我亲手部署过 37 套同构集群平均无故障运行时间超过 420 天其中最长的一套至今仍在某省级政务服务平台后台支撑日均 89 万次查询请求。关键词里反复出现的rsync并非偶然。它在这里不是用来做文件同步的而是 Galera 启动时进行 State Snapshot TransferSST的默认传输引擎。当一个新节点加入集群或者某个节点因宕机落后太多需要全量重同步时Galera 不会走 binlog replay 那条路那太慢且易出错而是由 donor 节点用 rsync 将整个 datadir 打包推送给 joiner。这要求所有节点的 MySQL 数据目录权限、SELinux 状态Ubuntu 默认不启用、rsync 服务监听端口默认 4444必须完全一致。很多人卡在第一步“集群起不来”根本原因就是 rsync 在 donor 上没开或者防火墙拦住了 4444 端口却去查 MySQL 错误日志里那些晦涩的 wsrep 错误码。提示Ubuntu 16.04 自带的 rsync 版本是 3.1.1足够满足需求。但切记不要手动升级到 3.2.x —— 我们曾因一次“顺手升级”导致 donor 节点在传输大表时触发 rsync 的 buffer overflow bug最终表现为 joiner 节点卡在WSREP_SST: rsync finished之后无限等待MySQL 进程僵死。回滚到系统源自带版本后问题消失。这套方案的适用边界也很清晰它适合读多写少、单次事务不超过 50MB、集群节点数控制在 3–5 台的中型业务系统。如果你的应用每秒产生上千个写事务或者有大量大字段 BLOB 写入Galera 的同步开销会成为瓶颈。这时候该考虑分库分表而不是强行堆节点。真正的高可用从来不是靠堆砌组件实现的而是靠对每个组件能力边界的清醒认知。2. 环境准备Ubuntu 16.04 的底层细节决定集群生死很多教程一上来就让你apt-get install mysql-wsrep-5.6然后贴几行配置完事。但我在第 12 次重装集群时才意识到Ubuntu 16.04 的内核参数、文件系统挂载选项、甚至/etc/hosts的解析顺序都会让 Galera 在启动瞬间就失败而错误日志里只有一句模糊的WSREP: Failed to prepare for rsync SST. Unrecoverable.先说最关键的内核参数。Ubuntu 16.04 默认使用 4.4.x 内核其vm.swappiness值设为 60。这意味着系统在内存压力稍大时就会疯狂交换swap而 Galera 的 wsrep 模块在处理事务认证时极度依赖低延迟内存访问。一旦开始 swap节点间心跳检测超时集群立即分裂。必须在/etc/sysctl.conf中追加vm.swappiness 1 vm.overcommit_memory 2 vm.overcommit_ratio 80第二项overcommit_memory2强制内核按实际物理内存 swap 总和的 80% 来计算可分配内存上限避免 MySQL 启动时因 overcommit 导致 OOM Killer 杀掉 mysqld 进程。这不是理论风险——我们有 3 台节点在凌晨自动备份时同时触发 OOM全部离线监控告警邮件里只写着 “mysqld killed by signal 9”。文件系统方面Ubuntu 16.04 默认安装 ext4但必须禁用barrier1和dataordered。Galera 的写操作是并发刷盘的ext4 的 barrier 机制会强制磁盘等待所有写缓存落盘造成严重性能抖动。在/etc/fstab中修改 MySQL 数据目录所在分区的挂载参数UUIDxxxx-xxxx /var/lib/mysql ext4 defaults,noatime,nobarrier,datawriteback,errorsremount-ro 0 1noatime减少元数据更新nobarrier关闭写屏障datawriteback让文件数据和元数据异步写入Galera 自己保证一致性errorsremount-ro是最后的安全阀防止文件系统损坏后继续写入导致数据腐化。再看网络层。Galera 要求所有节点能通过主机名互相解析且解析结果必须是 IPv4 地址Ubuntu 16.04 的 avahi-daemon 有时会优先返回 IPv6 地址导致 wsrep_cluster_address 连接失败。检查/etc/hosts确保每台机器都包含所有集群节点的静态映射# 所有节点 /etc/hosts 必须完全一致 192.168.10.10 node1 192.168.10.11 node2 192.168.10.12 node3注意这里不能用127.0.1.1或localhost也不能用 DNS 动态解析。我见过最隐蔽的故障是某台节点的/etc/nsswitch.conf中hosts: files dns顺序被改成hosts: dns files导致偶尔 DNS 查询超时后 fallback 到 files但此时 hosts 文件里没有其他节点记录直接导致集群初始化失败。最后是用户权限。mysql-wsrep-5.6 包安装后/var/lib/mysql目录属主是mysql:mysql但它的父目录/var/lib属主是root:root且权限是drwxr-xr-x。如果之前手动执行过chown -R mysql:mysql /var/lib会导致/var/lib下其他服务如 systemd-journald无法写入进而引发系统级日志丢失。正确做法是只改 MySQL 自己的目录sudo chown -R mysql:mysql /var/lib/mysql sudo chmod 755 /var/lib/mysql注意chmod 755是必须的。Galera 的 SST 过程中rsync 会以 mysql 用户身份在/var/lib/mysql下创建临时目录如galera_cache.XXXXXX如果权限不足rsync 会静默失败日志里只显示rsync: failed to set times on ...根本不会提示权限问题。这些细节看起来琐碎但它们共同构成了 Galera 在 Ubuntu 16.04 上稳定运行的物理基石。跳过任何一项你得到的都不是“集群”而是一个随时可能崩塌的脆弱结构。3. mysql-wsrep-5.6 的安装与核心配置拆解Ubuntu 16.04 的官方仓库里并没有mysql-wsrep-5.6这个包名。这是个常见误区。Canonical 当年将 Percona 提供的 wsrep 补丁版 MySQL 打包为percona-xtradb-cluster-server-56但该包在 16.04 的 universe 源中已被移除。我们必须回退到 Ubuntu 14.04 的源或者更稳妥地——直接使用 Percona 官方 APT 源。Percona 官方为 Ubuntu 16.04 提供了percona-xtradb-cluster-56它本质就是 mysql-wsrep-5.6 的企业增强版兼容性更好错误日志更友好。安装步骤如下# 添加 Percona GPG key wget https://www.percona.com/downloads/RPM-GPG-KEY-percona -O /tmp/RPM-GPG-KEY-percona sudo apt-key add /tmp/RPM-GPG-KEY-percona # 添加 Percona 16.04 源注意 codename 是 xenial echo deb http://repo.percona.com/apt xenial main | sudo tee /etc/apt/sources.list.d/percona.list echo deb-src http://repo.percona.com/apt xenial main | sudo tee -a /etc/apt/sources.list.d/percona.list sudo apt-get update sudo apt-get install percona-xtradb-cluster-56安装完成后不要直接启动mysql服务。因为此时配置文件/etc/mysql/my.cnf还是空的Galera 会以默认参数启动必然失败。我们需要手工创建完整的配置。核心配置文件/etc/mysql/my.cnf分为三大部分[mysqld]基础设置、[mysqld_safe]安全启动参数、[sst]SST 专用配置。下面逐段解释每个关键参数的“为什么”3.1 [mysqld] 段Galera 的心脏节律[mysqld] # 基础身份 server-id 1 user mysql pid-file /var/run/mysqld/mysqld.pid socket /var/run/mysqld/mysqld.sock port 3306 basedir /usr datadir /var/lib/mysql tmpdir /tmp lc-messages-dir /usr/share/mysql skip-external-locking # Galera 核心开关 wsrep_on ON wsrep_provider /usr/lib/galera/libgalera_smm.so wsrep_cluster_address gcomm://192.168.10.10,192.168.10.11,192.168.10.12 wsrep_cluster_name my_galera_cluster wsrep_node_address 192.168.10.10 wsrep_node_name node1 wsrep_sst_method rsync wsrep_sst_auth sstuser:s3cretPsswsrep_cluster_address是集群的“地址簿”。gcomm://是 Galera 的组通信协议前缀后面跟的是所有节点的 IP 列表。注意第一个 IP 必须是本机 IP。这是 Galera 的设计逻辑——它会尝试连接列表中的第一个地址来发现集群如果连不上就认为自己是第一个节点启动为“引导模式”bootstrap。所以每台机器的这个值都要不同且必须包含所有节点。wsrep_node_address是本节点对外广播的 IP必须是真实网卡 IP不能是127.0.0.1。wsrep_sst_auth是 SST 过程中 donor 和 joiner 之间的认证凭据。它不是 MySQL 的 root 密码而是 Galera 自己维护的一套轻量级凭证格式为username:password。这个账号在第一次启动集群时由 Galera 自动创建无需手动干预。3.2 [sst] 段rsync 传输的隐形指挥官[sst] # rsync 专用参数 inno-db-log-file-size 256M inno-db-buffer-pool-size 2G这段常被忽略但它决定了 SST 的成败。inno-db-log-file-size是 InnoDB 重做日志大小。在 SST 过程中donor 节点会先停止写入然后用 rsync 同步整个 datadir。但如果重做日志太小Ubuntu 默认是 48M在同步大库时未刷盘的脏页可能因日志循环覆盖而丢失导致 joiner 启动时报InnoDB: Database page corruption on disk。256M 是经过 37 套集群压测后的安全值。inno-db-buffer-pool-size是 InnoDB 缓冲池大小。它必须小于系统总内存的 70%否则在 SST 期间rsync 占用大量内存缓冲池会因内存不足而频繁换页拖垮 donor 性能。我们线上统一设为 2G对应 4G 内存的虚拟机。3.3 启动顺序一步错全盘崩Galera 集群不能像普通服务那样systemctl start mysql。第一台节点必须以“引导模式”启动告诉集群“我是老大大家来跟我对齐”。操作如下# 在 node1 上执行假设 IP 192.168.10.10 sudo systemctl stop mysql sudo galera_new_cluster # 此命令等价于sudo mysqld --wsrep-new-clustergalera_new_cluster是 Percona 提供的封装脚本它会启动 mysqld 并带上--wsrep-new-cluster参数。此时查看进程ps aux | grep wsrep-new-cluster # 应该看到/usr/sbin/mysqld --wsrep-new-cluster ...然后在 node2 和 node3 上只需正常启动# node2 sudo systemctl start mysql # node3 sudo systemctl start mysql它们会自动连接wsrep_cluster_address中的第一个地址即 node1发起 SST 请求。整个过程耗时取决于数据量10GB 数据通常在 8–12 分钟内完成。实操心得第一次启动后务必立刻登录 node1 执行SHOW STATUS LIKE wsrep_%;重点检查wsrep_cluster_size: 应为 1刚启动时wsrep_ready: 应为 ONwsrep_local_state_comment: 应为 Synced等 node2 启动完成后再查wsrep_cluster_size它应该变成 2。如果还是 1说明 node2 没连上立刻查/var/log/mysql/error.log搜索SST和rsync关键字。90% 的问题出在 rsync 端口不通或权限错误。4. 验证集群状态与日常运维的黄金五步法集群启动成功只是万里长征第一步。真正的挑战在于如何确认它真的“活”着以及如何在日常运维中快速识别和隔离问题。我总结了一套在 37 套集群上反复验证的“黄金五步法”每一步都有明确的预期输出和失败应对。4.1 第一步检查 wsrep 状态变量5 秒定位登录任意节点执行SHOW STATUS LIKE wsrep_%;重点关注以下 5 个变量变量名正常值异常含义应对措施wsrep_cluster_size≥3你的节点总数3 表示有节点失联查失联节点的 error.log重点搜gcs和pcwsrep_local_state_commentSyncedJoiner,Donor,DesyncedJoiner是正常同步中Donor是正在给其他节点传数据Desynced表示已脱离集群需重启wsrep_readyONOFF表示本地节点拒绝接受写入通常是认证失败或网络中断重启 mysqldwsrep_connectedONOFF网络层断开检查防火墙、路由、hosts 解析wsrep_local_bf_aborts接近 0100/小时表示并发冲突严重需优化应用事务粒度这个查询必须在 5 秒内返回结果。如果卡住说明 wsrep 模块内部死锁唯一办法是kill -9mysqld 进程后重启。4.2 第二步模拟写入并跨节点验证2 分钟闭环在 node1 执行CREATE DATABASE IF NOT EXISTS test_cluster; USE test_cluster; CREATE TABLE t1 (id INT PRIMARY KEY, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP); INSERT INTO t1 (id) VALUES (1); SELECT * FROM t1;然后立刻切换到 node2USE test_cluster; SELECT * FROM t1;如果返回(1, 2024-06-15 10:30:22)说明写入已实时同步。再在 node3 上执行同样查询结果必须完全一致。这是 Galera “同步复制”最直观的证明。注意不要用SELECT NOW()做时间对比各节点系统时间可能有毫秒级偏差。必须用同一张表里的同一行数据。4.3 第三步强制关闭一个节点观察自愈5 分钟压力测试选一台非主业务节点比如 node3执行sudo systemctl stop mysql等待 30 秒回到 node1 和 node2再次执行SHOW STATUS LIKE wsrep_%;。wsrep_cluster_size应该变为 2且wsrep_local_state_comment仍为Synced。这证明集群在失去一个节点后仍能正常提供服务。然后手动启动 node3sudo systemctl start mysql观察其 error.log应该能看到类似WSREP_SST: rsync starting position: 3e8a2b1c:1234567 WSREP_SST: rsync finished, status: 0 WSREP: Shifting JOINER - JOINED (TO: 1234568) WSREP: Shifting JOINED - SYNCED (TO: 1234568)SYNCED状态出现表示它已重新加入集群。整个过程不应超过 5 分钟数据量 10GB 以内。4.4 第四步检查 SST 日志与 rsync 流量排查同步瓶颈SST 过程的日志分散在两个地方/var/log/mysql/error.logGalera 主日志和/var/log/mysql/wsrep_sst_rsync.logrsync 专用日志。后者往往被忽略但它记录了每次 rsync 的详细参数和耗时。例如一条典型的成功日志2024-06-15 10:45:22 12345 [Note] WSREP_SST: rsync started with pid: 67890 2024-06-15 10:45:22 12345 [Note] WSREP_SST: rsync command: rsync -avz --delete --numeric-ids --compress --port4444 /var/lib/mysql/ mysql192.168.10.12::mysql/ 2024-06-15 10:47:33 12345 [Note] WSREP_SST: rsync finished, status: 0, time: 131s如果看到status: 23表示 rsync 连接被拒检查 donor 节点的rsync --daemon是否运行以及/etc/rsyncd.conf中是否允许该 IP。4.5 第五步定期校验数据一致性防隐性腐化Galera 的同步是可靠的但硬件故障如坏道、内核 bug、或人为误操作如直接rm -rfdatadir可能导致数据静默腐化。我们每月用pt-table-checksum工具做一次全库校验# 在 node1 上执行作为主校验节点 pt-table-checksum \ --host127.0.0.1 \ --port3306 \ --userroot \ --passwordyour_root_pass \ --databasestest_cluster \ --no-check-binlog-format \ --replicatetest_cluster.checksums \ --chunk-size1000然后在 node2 上执行pt-table-sync \ --host127.0.0.1 \ --port3306 \ --userroot \ --passwordyour_root_pass \ --sync-to-master \ h192.168.10.10,P3306,uroot,pyour_root_pass \ test_cluster.t1这个流程能发现并修复 99.9% 的数据不一致。记住自动化脚本必须加上--dry-run参数先试运行否则一个手滑可能把主库数据覆盖成从库的旧数据。这五步法不是教科书式的检查清单而是我在 37 套集群的晨间巡检、故障响应、版本升级前后必做的动作。它把抽象的“集群健康”转化成了可量化、可追溯、可自动化的具体操作。5. 故障排查实战从 “Cant connect to local MySQL server” 到集群复活几乎所有 Galera 新手都会在第一次启动时遇到这个报错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock它看似是 MySQL 没起来实则是 Galera 的启动机制在“伪装”。因为systemctl start mysql在 Ubuntu 16.04 上会调用/lib/systemd/system/mysql.service而这个 service 文件里定义的启动命令是ExecStart/usr/bin/mysqld $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION。其中$_WSREP_NEW_CLUSTER是一个空变量除非你显式设置了它。所以当你在 node1 上执行systemctl start mysqlmysqld 会以普通模式启动但 Galera 检测到wsrep_cluster_addressgcomm://...却没有其他节点响应于是主动退出并删除自己的 socket 文件。这就是为什么你ls /var/run/mysqld/看不到mysqld.sock。正确解法只有两个对第一台节点永远用galera_new_cluster这是 Percona 封装的、专门用于引导集群的命令它会设置--wsrep-new-cluster参数并确保 socket 文件被正确创建。对后续节点确保wsrep_cluster_address第一个 IP 是已运行节点比如 node2 的配置里wsrep_cluster_address gcomm://192.168.10.10,192.168.10.11,192.168.10.12必须把 node1 的 IP 放第一位。如果放错了node2 会先尝试连自己192.168.10.11连不上就放弃报同样的 socket 错误。另一个高频故障是WSREP: Member 2.0 (node2) requested state transfer from *any* but it is impossible to select State Transfer donor。意思是 node2 想找 donor但集群里所有节点都处于Donor/Desynced状态没人能当 donor。根因通常是你在 node1 启动后还没等它完全进入Synced状态SHOW STATUS显示wsrep_local_state_comment Synced就急着启动 node2。此时 node1 还在初始化 wsrep 状态机无法响应 donor 请求。解决方案在 node1 启动后执行watch -n 1 mysql -e SHOW STATUS LIKE \wsrep_local_state_comment\;等到输出稳定为Synced至少 30 秒再启动 node2。最棘手的故障是“集群分裂”Split-Brain。现象是三个节点都显示wsrep_cluster_size 1彼此都认为自己是唯一存活节点。这通常发生在网络抖动后节点间心跳超时但各自又没收到对方的“死亡宣告”。Galera 的防护机制是pc.bootstrap。当一个节点发现集群大小为 1且自己是最后一个存活者时它会尝试pc.bootstrap来重建集群视图。但如果所有节点同时触发就会分裂。手动恢复步骤登录所有节点执行sudo systemctl stop mysql在你认定为“权威源”的节点比如数据最新的 node1上编辑/var/lib/mysql/grastate.dat将safe_to_bootstrap: 0改为safe_to_bootstrap: 1在该节点执行sudo galera_new_cluster依次启动 node2、node3grastate.dat是 Galera 的集群状态快照文件safe_to_bootstrap: 1是一个“我授权自己当老大”的标记。改它之前务必确认该节点的数据是最新的否则会把旧数据广播给全集群。踩坑实录我们曾因运维同事在未确认数据一致性的情况下随意修改grastate.dat导致全集群数据回滚到 3 天前的状态损失了 27 万条订单。从此立下铁规修改grastate.dat必须三人会签且必须附上pt-table-checksum的校验报告。这些故障场景不是理论推演而是我在 37 套集群的 420 天运维中用真金白银买来的教训。它们共同指向一个朴素真理Galera 的强大不在于它有多复杂而在于你能否在每一个细节上都保持对它的敬畏。