
凌晨一点半监控把电话打到手机上主库磁盘IO报警从库延迟已经冲到四千多秒。那会儿我正给一个电商平台做数据库层改造用的还是传统的MySQL主从复制。写压力全部集中在主库从库追不上主库一旦宕机就要人肉切换应用还得改连接地址重启整个过程快则二十分钟慢起来要一个小时。也就是从那个项目开始我下定决心把核心库迁到MariaDB Galera集群前面再加一层ProxySQL做完全读写分离。这套组合跟我们一起扛过多次节点宕机和流量高峰今天把Galera的高可用机制和ProxySQL的读写分离研究整理出来给正在选型或者准备落地的同行做一个参照。内容比较长按照先讲原理、再讲落地、最后讲踩坑的逻辑展开。节点规划用三台机器192.168.10.11、192.168.10.12、192.168.10.13系统是CentOS 7.9数据库用MariaDB 10.6Galera 4ProxySQL 2.4。如果你手头的环境不是这套版本组合参数名和包名会有差异但整体思路完全通用。1. 为什么我把高可用方案从主从复制换成了Galera集群1.1 传统主从复制的痛点先说清楚我原来那套主从复制的问题因为很多做DBA或者后端的朋友现在还停留在这个阶段。理解了换方案的动机后面看Galera的机制才不会觉得玄。主从复制最大的问题有两个延迟和切换成本。延迟方面异步复制下从库落后主库几百上千秒是家常便饭高峰期一冲就是几万秒binlog全堆在从库的relay log里。为了减少延迟我们试过增强半同步但半同步要求主库写入后等待从库ACK写多的时候性能立刻塌下来。切换成本方面主库挂掉之后要判断哪个从库数据最完整可能要补日志、改VIP指向应用层还得改配置。整个过程要么人工介入要么依赖自定义HA脚本出一次事故少说要折腾半小时。更麻烦的是脑裂场景。主备模式里如果仲裁逻辑有问题就可能出现两个主库同时接受写入数据开始分叉最后只能靠人工核对修数据。说实话对数据一致性要求高、RPO希望为0的业务来说传统主从方案很难给到安全感。1.2 Galera的同步复制到底解决了什么问题MariaDB Galera集群用的是同步多主复制底层是Codership的Galera库通过wsrep API接进MariaDB。它的核心逻辑是任何一个节点收到事务提交请求会把写集合广播给集群内其他所有节点其他节点执行认证后要么全部提交要么全部回滚不存在一个节点提交了、另一个节点还停留在老数据的情况。这个机制带来的直接好处是读任何节点的数据都能看到集群里已经提交的最新状态不会出现主从延迟导致的读不到刚写完的数据问题。对业务方来说RPO几乎为0对DBA来说不用再天天盯着Seconds_Behind_Master。但这不代表Galera没有代价后面我会专门讲它在并发写和网络方面的限制。1.3 这个组合适合什么业务场景把Galera加ProxySQL的组合想清楚再用它适合几个典型场景一是读写比高、读流量特别大需要用多节点分摊读压力二是写并发可控、事务不要太大三是希望任何一个节点宕机都不影响写入四是网络在同一个机房、节点间延迟控制在毫秒级以内。反过来有些场景千万别硬套。比如跨机房部署网络延迟一高Galera的写性能会直线下降。再比如热点行更新特别密集、单事务更新几百万行的场景认证冲突会频繁出现越写越慢。在做任何技术选型之前先拿这个判断基准过一遍业务情况别等上了生产再后悔。2. Galera集群的高可用机制核心拆解2.1 认证与状态转移数据一致性怎么保证Galera的提交协议可以理解成一场先记账、后出库的交易。客户端发来一个事务节点先把写集合打包广播其他节点收到之后跑一轮认证检查事务涉及的数据在集群全局事务序列里有没有冲突。没冲突认证通过大家各自在本地实例里提交有冲突先到的事务胜出后到的被回滚客户端会收到死锁或认证失败的错误。这里最容易被忽略的是binlog格式必须设为ROW。Statement格式下很多操作在解析层面就不一致Galera直接要求ROW模式不然节点间的写集合校验没法做。另一个点是事务只在一个节点上执行但这个节点的提交动作会变成所有节点的提交动作所以应用任何时候只需要连一个可用节点写的分布由应用层或ProxySQL去分摊。节点状态机上那套缩写也得认识一个节点初始加入集群经历JOINER正在同步、JOINED已加入但没到SYNCED、SYNCED完全同步可对外服务、DONOR把自己的数据同步给别人、DESYNCED主动脱离同步。日常巡检最关心的就是wsrep_local_state_comment只要不是SYNCED这个节点就不该接业务流量。2.2 节点状态机与脑裂防护高可用系统里脑裂是个永恒话题Galera的防护思路是法定人数Quorum。集群里每个节点都维护一个Primary Component概念正常时候所有节点构成一个Primary组件。网络分区一旦发生某个分区如果能凑够超过半数的节点那么这个分区继续以Primary身份对外服务不足半数的分区会把自己标记为Non-Primary拒绝一切读写请求宁可停服也不制造脑裂。这个设计很实用三节点集群挂掉一个剩下两个仍然超过半数继续工作挂掉两个只剩一个剩下的节点会进入Non-Primary不再接受任何SQL。很多刚接触Galera的人会遇到为什么还有节点活着但写不进去的情况其实就是这个策略在起作用。这个时候你需要把故障节点恢复或者确认集群其他节点数据完整后手动把数据最新的节点配置为safe_to_bootstrap再拉起来。2.3 全量同步与增量同步的协作逻辑节点加入集群时如果本地已经积累了大部分事务日志走的是IST增量状态传输只补缺失的那段事务如果本地数据太旧或gcache覆盖不了缺失区间就走SST全量状态快照把整份数据同步过来。SST方法我推荐mariabackup在线完成不停业务对InnoDB支持也好老版本的rsync虽然简单但会把节点整个数据目录直接拷过去源节点压力极大而且严格来说不算一致性快照。SST期间性能损耗很明显几GB数据可能就要几分钟到几十分钟业务高峰期千万别随便在集群里加节点。日常运维最好把gcache.size调大一点让离线短时间的节点尽量走IST几秒十几秒就能追回来而不是动不动全量SST。Galera还有流控机制如果最慢的节点落后超过阈值所有节点的提交都会被暂停直到它追上。这保证了慢节点不会变成黑洞但也意味着单台机器性能差会拖累整个集群的写吞吐所以节点之间硬件尽量保持同规格。3. 实际操作从三台裸机到可用集群3.1 安装与基础配置三台机器统一最小化安装CentOS 7配好官方源或国内镜像源再用chrony把三台时间同步一致。Galera对节点间时间偏差很敏感时间不同步是第一次搭建就翻车的高频原因。安装命令比较简单yum install -y mariadb-server mariadb-backup galera-4装完先别急着起服务修改 /etc/my.cnf.d/galera.cnf关键配置如下表。如果没有这个文件直接在 /etc/my.cnf 追加也可以配置项推荐值说明wsrep_provider/usr/lib/galera/libgalera_smm.soGalera库路径wsrep_cluster_namemycluster三台必须一致wsrep_cluster_addressgcomm://192.168.10.11,192.168.10.12,192.168.10.13初始成员列表wsrep_node_address各自IP节点本机IPwsrep_node_namenode1/node2/node3自定义节点名wsrep_sst_methodmariabackup全量同步方式wsrep_sst_authsstuser:passwdSST账号密码binlog_formatROW必须ROWinnodb_autoinc_lock_mode2避免主键锁冲突innodb_flush_log_at_trx_commit2性能和持久化平衡binlog_format在默认配置里可能不会显式写出来建议三个节点都显式指定。wsrep_sst_auth对应的账号需要在第一个节点启动后创建否则其他节点做SST时连不上源节点。还有bind-address别只绑127.0.0.1否则跨节点通信直接失败这个我至少见过三个人栽在这里。3.2 按顺序启动节点的细节启动顺序是Galera新手最容易栽的坑。第一次初始化集群只能由第一个节点执行galera_new_cluster systemctl start mariadbgalera_new_cluster的作用是用--wsrep-new-cluster方式拉起一个初始主节点并把它的grastate.dat标记为safe_to_bootstrap: 1。剩下两台不要执行任何特殊命令直接systemctl start mariadb它们会从集群地址列表里发现已有集群并加入。如果第二、三台也用galera_new_cluster启动等于启动了三个互不相识的独立集群后面全乱套。另一种高频故障是集群此前正常关闭过一次比如机房整体断电重启时不知道从哪个节点拉起。正确做法是看每台机器的 /var/lib/mysql/grastate.dat找到safe_to_bootstrap: 1的那台确认它的seqno在所有节点里最大从它开始galera_new_cluster。seqno最大意味着它的数据是集群里最新的。这个判断在后续故障演练章节还会细讲。3.3 验证集群健康状态的手段集群起来之后先做一轮健康检查我一般用这几个SQL组合SHOW STATUS LIKE wsrep_cluster%; SHOW STATUS LIKE wsrep_local_state%; SHOW STATUS LIKE wsrep_connected; SHOW STATUS LIKE wsrep_ready;三台机器上wsrep_cluster_size都显示3wsrep_local_state_comment都是SYNCEDwsrep_connected为ONwsrep_ready为ONwsrep_cluster_status为Primary基本可以认为集群正常。然后建一个测试库往任意一台写数据在另外两台查一下能不能立刻看到这一步能验证同步复制是否真正生效。跑通之后别忘了创建SST账号GRANT ALL ON *.* TO sstuserlocalhost IDENTIFIED BY 你的密码;如果SST要跨节点访问授权范围要写成所有节点IP或整个内网网段。另外多说一句不要在生产环境里长期使用mariadb的root空密码或默认弱密码先把root密码设好后面第5章会专门讲这个很多人搜给mariadb root设置密码就是因为不熟悉MariaDB默认的unix_socket认证方式。4. ProxySQL介入后的完全读写分离实现4.1 ProxySQL为什么比应用层读写分离靠谱很多团队试过在应用层自己搞读写分离写走主库连接串读走只读连接串。听起来简单实操起来全是坑。每个服务都要维护两套连接配置一个查询是读是写往往要写一堆判断代码主从切换的时候应用还得发版改配置。更麻烦的是连接池一个服务建了几百个连接后端地址一变就要重启一批服务。ProxySQL站在MySQL协议层面做代理客户端只连它一个地址它内部维护读写两组后端节点按规则分发流量。后端节点挂了ProxySQL自动把流量转移到健康节点应用完全无感知。连接复用也做得好前端几千个连接到后端MariadB的真实连接可以少一个数量级这对MariaDB的连接上限压力小很多。对Galera来说因为多主同步读流量可以分到所有节点这一层代理的价值会被放大。4.2 hostgroup与路由规则配置ProxySQL的核心概念是hostgroup一组同职责的MariaDB节点。我习惯把写节点放进hostgroup 10读节点放进hostgroup 20。由于Galera多主理论上三台都能写但为了减少认证冲突和事务跨节点概率我建议写流量固化到一台读流量在三台之间轮询或加权这仍然比传统架构灵活得多。先在admin控制台里注册后端默认管理端口是6032mysql -h127.0.0.1 -P6032 -uadmin -padminINSERT INTO mysql_servers (hostgroup_id, hostname, port, weight) VALUES (10, 192.168.10.11, 3306, 1), (20, 192.168.10.11, 3306, 1), (20, 192.168.10.12, 3306, 1), (20, 192.168.10.13, 3306, 1);这里有个容易漏掉的点同一个物理节点可以同时出现在多个hostgroup里ProxySQL按hostname加port区分节点所以不需要额外建什么虚拟标识。再注册应用账号和路由规则INSERT INTO mysql_users (username, password, default_hostgroup) VALUES (appuser, apppass, 10); INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (1, 1, ^SELECT.*FOR UPDATE, 10, 1), (2, 1, ^SELECT, 20, 1), (3, 1, ^(INSERT|UPDATE|DELETE|REPLACE), 10, 1);路由规则的处理顺序是从小到大匹配rule_id命中后如果apply为1就停止继续匹配。SELECT...FOR UPDATE必须走写组因为它是行级锁操作必须直接面对正在写入的数据。普通SELECT走读组写入语句走写组。规则配完记得执行LOAD MYSQL QUERY RULES TO RUNTIME; LOAD MYSQL SERVERS TO RUNTIME; LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK; SAVE MYSQL SERVERS TO DISK; SAVE MYSQL USERS TO DISK;否则配置只在内存里重启就没了。ProxySQL自带定时健康检查默认每隔几秒对后端做连接和心跳测试失败的节点会被自动移出轮询列表。对Galera集群来说心跳连通不代表节点可用因为节点可能处于Non-Primary或者正在做SST所以建议挂上官方提供的galera_checker.sh脚本放到调度器里定期检查wsrep状态把不健康的节点临时摘掉。这一步做到了完全读写分离的定义才真正完整读请求不会落到任何一个状态异常的节点上。4.3 读写分离验证与监控配置完之后不要急着上线先用ProxySQL的入口执行几类典型SQL然后去stats库看路由结果SELECT hostgroup, schemaname, digest_text, count_star FROM stats_mysql_query_digest ORDER BY count_star DESC;统计结果里能看到同样一条SELECT落在hostgroup 20上INSERT落在hostgroup 10上FOR UPDATE的查询落在10上。再配合SELECT * FROM stats_mysql_connection_pool;可以看每个后端当前连接数和查询数。实际压测中我发现一个现象做完读写分离后读流量虽然分到了多个节点但如果连接池预热不足刚压上去时部分读节点连接数飙升过几分钟才平稳。所以压测数据要等预热稳定后再统计别拿前几分钟的数据判断容量。5. 顺手解决的热点问题给MariaDB root设置密码与用户安全5.1 新装实例的root密码初始化最近有个被频繁搜索的问题就是给MariaDB root设置密码。这也是很多新手在MariaDB上比在MySQL上更容易困惑的地方。原因在于MariaDB从10.4开始root默认走unix_socket认证本地用操作系统root身份直接执行mysql就能免密进入。这个设计本意是安全但业务人员总觉得没设密码不安全应用连接如果要用root也会遇到麻烦。正确初始化方式有两种。临时方法是在用socket进去后执行ALTER USER rootlocalhost IDENTIFIED BY 强密码;这会覆盖默认的unix_socket认证方式。如果想保留root本地免密同时给远端应用一个专用账号更推荐的做法是别动root直接建应用账号。如果确实要走传统密码模式还要确认/etc/my.cnf里没有skip-grant-tables之类的调试参数。设置完验证一下exit后用 mysql -uroot -p 尝试密码登录能进就说明密码生效了。在Galera集群中这个ALTER USER会在所有节点同步所以只需要执行一次。5.2 Galera环境下用户权限同步的注意点Galera环境下用户和权限的变更会通过复制传递到所有节点所以在一台机器上执行ALTER USER或GRANT其他节点会自动应用不需要挨个设置。但有一个细节权限操作必须用标准SQL语句不要直接往mysql.user表里INSERT因为底层表结构在不同版本间差异很大绕过权限系统直接改系统表在某些版本会触发同步校验失败。另外一个实际经验是Galera加ProxySQL这套架构里真正对外提供连接的是ProxySQL应用并不直接连MariaDB节点。所以应用账号既要存在于MariaDB各节点保证后端连接能认证也要在ProxySQL的mysql_users里登记。两边密码不一致时ProxySQL会反复建连失败但客户端看到的是连接超时不太容易立刻想到密码问题。我排查这类问题时会先看ProxySQL的stats库错误计数能快速定位是不是认证失败。5.3 ProxySQL中用户与密码的管理细节ProxySQL的账号分两层。管理账号admin默认在6032端口监听用默认账号密码登录后可以执行所有配置变更转发给后端的客户端账号则是在mysql_users表里登记。举个例子INSERT INTO mysql_users (username, password, default_hostgroup) VALUES (appuser, 客户端密码, 10); LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;需要特别注意ProxySQL并不加密存储后端密码的原文只是在配置层做了混淆。所以生产环境的应用密码不要设成和root同一个也别让脚本把密码打到日志里。我的习惯是应用密码每个季度轮换一次轮换时先改MariaDB侧密码再改ProxySQL侧两边SQL按顺序执行中间会有一个短暂的重连窗口选在业务低峰做。6. 故障演练与踩坑记录6.1 节点宕机恢复的完整过程方案搭完后我们做过几次有计划的故障演练最有价值的一次是直接拔掉node2的网线。结果符合预期node1和node3继续提供读写业务没有任何感知。node2恢复网络后MariadB服务自动或手动重启观察它的状态从JOINER一路到SYNCED。因为离线时间短走了IST几秒钟就追平了。这个演练验证了两件事集群的法定人数逻辑在2/3节点存活时能正确运行IST的快速追平让节点回巢成本很低。真正危险的是全集群断电后同时重启。那个场景下所有数据库都正常关闭过grastate.dat里的seqno都有值你需要对比三个节点的seqno挑seqno最大的那个作为启动起点把它的safe_to_bootstrap设为1执行galera_new_cluster其他节点再正常启动。如果图省事随便挑一个节点先启大概率启动失败甚至可能让集群以旧数据为基础重建丢失新数据。这个教训是我们在一次机房断电演练中真实遇到的。6.2 我踩过的三个坑第一个坑是gcache.size默认值太小。默认1G左右如果节点离线超过一小会儿之前的事务日志就被循环覆盖了节点回巢只能走SST。我们当时数据量上TSST一次要三四十分钟运维窗口完全顶不住。后来把gcache.size调到4G再把节点离线时间控制在几分钟内绝大多数回巢都走IST。第二个坑是各节点的max_connections不一致。Galera的DDL和状态信息可以在节点间同步但连接数上限不会自动同步。应用连接池在某个节点上额度满了ProxySQL把请求继续发过去就会偶发too many connections非常难排查。解法是把所有节点的连接上限统一设置并且ProxySQL的max_connections也留出余量。第三个坑是大事务和长查询对流控的影响。我们有个统计任务在凌晨一次性UPDATE了几百万行结果整个集群的提交都被流控卡住线上正常业务出现秒级写抖动。后来把大任务拆成多批小事务并且错开业务高峰问题就消失了。这个坑提醒我Galera对事务大小非常敏感任何批量任务上线前都要先评估拆分方案。6.3 性能观察与优化建议跑稳定之后我把几个关键参数又做了微调。innodb_flush_log_at_trx_commit从默认1改成2配合Galera自身的同步保证写性能提升明显而持久化风险基本被集群多副本覆盖。读节点上把wsrep_sync_wait按需设置平衡读写一致性窗口。ProxySQL方面如果是多实例部署我会用两个ProxySQL节点做VIP漂移避免代理层成为新的单点同时监控stats_mysql_connection_pool里每台后端的长连接数发现倾斜就调整weight。网络层面也有心得三台节点之间走万兆内网启用Jumbo Frame后大事务的广播延迟明显下降。别小看这个Galera所有写的延迟都取决于最差那条广播链路的延迟网络抖动直接等于写入抖动。有条件的话交换机端口隔离、防火墙只放行3306和4567、4568端口的必要通信不要为了图方便全部放开。最后再补充一个容易被忽略的细节ProxySQL的galera_checker脚本默认会探测所有节点的wsrep状态但脚本跑起来需要能连接各节点所以它的监控账号权限要提前建好别等上线后发现脚本一直在报错。我在实际操作中习惯把脚本日志单独落一份文件每次节点恢复后先看脚本日志再决定要不要人工介入。这套Galera加ProxySQL的组合在我们这里已经稳定运行了一年多最大的感触是高可用不是切换快而是每个节点都具备同等的读写能力。真正理解同步复制的代价和边界再配合代理层的健康感知和路由控制数据库这块的安全感才算真正建立起来。