ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

双主热备架构实战:MySQL高可用设计、切换与脑裂防治

双主热备架构实战:MySQL高可用设计、切换与脑裂防治 双主热备也叫双活主备、Active-Active这个概念我这些年几乎在每个需要高可用的系统里都会碰到它。它不像传统的主从架构那样主库挂了从库只能干瞪眼等着手动切换双主架构的核心思路是让两个节点同时承担读写流量任何一个节点宕机另一个节点能立刻接管全部请求业务几乎无感知。这篇博文我会结合我自己实操过的数据库、缓存和中间件场景把双主热备的设计思路、实现方案、配置细节和那些文档里不会写的坑一次性讲透。1. 双主热备到底解决什么问题1.1 传统主从架构的痛点很多团队刚开始做高可用时第一反应是搭一套主从复制。MySQL主从、Redis主从听起来简单但真到线上出故障时你会发现一堆麻烦。首先是切换成本高。主库宕机后从库虽然数据完整但应用层连接的是主库IP你得手动改配置、重启应用或者依赖第三方工具做VIP漂移。这个过程中业务是中断的而且人工操作极易出错——我见过凌晨三点切换主从时把从库只读忘关掉结果写入直接失败业务挂了半小时才被发现。其次是资源利用率低。从库明明有能力承担读流量但很多团队出于安全考虑只让它做备份或者只做异步延迟复制导致硬件成本翻倍但吞吐能力没提升。尤其到了大促或流量高峰主库压力拉满从库闲着这是非常浪费的。第三是数据一致性风险。MySQL默认的异步复制主库事务提交后从库可能还差几条数据。如果此时切主这几条数据就永久丢失了。半同步复制能缓解但性能损耗明显而且网络抖动时会退化成异步模式风险并没消失。1.2 双主热备的核心设计理念双主热备的名字容易让人误解以为两个节点同时处理一切流量。实际上真正的双主方案大多数时候仍会有流量偏向——比如通过VIP或负载均衡器把写流量集中到其中一个节点另一个节点实时同步数据并承担读流量如果写主节点故障另一个节点自动升级为写主整个过程无需人工介入。这里的关键是热备两个字。热备意味着备节点不是冷启动的备份机它保持着完整的数据状态随时可以接管。双主则是更进一步两个节点在逻辑上地位对等都有完整的数据副本都能对外提供服务。这样设计的好处是故障切换时间从分钟级降到秒级甚至亚秒级两个节点的硬件资源都被有效利用而不是一个忙死一个闲死支持计划内维护比如升级内核、替换硬件时可以先切换流量再操作业务不中断1.3 哪些场景真正适合双主不是所有系统都需要双主它带来的复杂度是实打实的。我个人的判断标准是业务连续性要求高RTO恢复时间目标要求小于30秒甚至趋近于零数据库或中间件是单点瓶颈且无法通过分库分表解决团队有人有能力处理双写冲突、脑裂等复杂问题预算足够覆盖双倍硬件成本如果你做的只是一个内部管理系统维护窗口可以安排在深夜那主从架构甚至单机加备份就够了没必要为了炫技上双主。双主方案是一把好刀但要看你会不会用。2. 双主热备的几种实现套路2.1 基于共享存储的双主方案这套路最常见于传统企业级环境比如SAN存储加集群文件系统。两个节点连接到同一套磁盘阵列通过集群管理软件如Veritas Cluster Server、Oracle RAC协调访问。EMC VNX这类存储设备更换故障热备盘的操作就是在这种架构下做的底层数据其实只有一份靠锁机制保证一致性。这个方案最大的优点是数据一致性极好因为不存在两份数据复制的问题。缺点也很明显存储本身成了新的单点存储挂了所有节点都挂而且SAN设备价格感人一般中小企业扛不住。2.2 基于数据复制软件的双主方案这种方案不共享存储每个节点有自己的本地磁盘通过数据复制软件如Oracle DataGuard、MySQL组复制、MongoDB副本集在节点间同步数据。应用层通过连接池或中间件把流量分发到两个节点。以MySQL为例从MySQL 5.7开始官方提供了组复制Group Replication和InnoDB Cluster底层是Paxos协议多个节点之间通过共识算法保证数据一致性。这套方案比传统的主从复制要安全得多因为它内置了冲突检测和事务认证机制。Redis的双主相对简单因为Redis的数据结构和操作语义决定了冲突场景有限。但要注意Redis主从复制是异步的网络分区时可能丢数据做双主前要评估业务能否接受。2.3 基于消息队列最终一致的双主方案这种方案不追求数据库层面的实时同步而是通过消息队列如Kafka、RocketMQ把写操作异步分发到另一个节点。每个节点有独立数据库写入时先发消息消费者在其他节点回放消息完成数据同步。这套方案的优点是完全解耦两个节点甚至可以用不同的数据库版本或存储引擎。缺点是数据最终一致中间可能会有短暂的不一致窗口而且消息积压或消费失败时数据偏差会持续累积需要有完善的补偿机制。2.4 选型时我建议怎么判断我自己的经验是优先考虑数据库自带的高可用方案而不是自己去写同步逻辑。数据库厂商花了几十年解决分布式一致性问题造轮子的成本和风险都太高。真到了必须要自研双主的时候说明你的数据模型已经特殊到通用的复制机制无法覆盖了这种情况非常罕见。另外要关注团队的技术栈熟悉程度。如果团队对MySQL比较熟InnoDB Cluster就是最稳妥的选择如果已经在用Paxos/Raft做分布式协调那组复制或自研方案也顺理成章。选型本质上是团队能力和业务需求的匹配问题。3. 实操记录用MySQL搭建一组双主热备3.1 环境准备我这里以MySQL 8.0为例操作系统是Ubuntu 22.04。准备两台服务器IP分别是192.168.1.10和192.168.1.11两台机器都安装MySQL 8.0.32。安装完成后先确认server_id不同后面配置要保证唯一性。# 第一台机器 sudo mysql -e SELECT server_id; # 期望输出: 1在配置文件中设置 # 第二台机器 sudo mysql -e SELECT server_id; # 期望输出: 2两台机器都需要开启binlog和GTID模式这是双主复制的基础。# /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] server-id 1 log-bin mysql-bin binlog_format ROW gtid_mode ON enforce_gtid_consistency ON log_slave_updates ON第二台机器把server-id改成2其他配置相同。注意log_slave_updates必须开这样从节点收到的变更会被写进自己的binlog继续传给其他节点。如果关掉这个参数A-B-C的复制链会断掉。3.2 初始化复制用户在两台机器上都创建复制账号推荐用专用账号别用root。CREATE USER repl% IDENTIFIED BY StrongPass2024; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES;这里用%是为了方便故障切换时互相连接但要注意防火墙只放行两台机器之间的3306端口别暴露公网。生产环境里建议限制IP段别图省事。3.3 配置双向复制在A机器上执行CHANGE MASTER TO MASTER_HOST192.168.1.11, MASTER_USERrepl, MASTER_PASSWORDStrongPass2024, MASTER_AUTO_POSITION1; START SLAVE;在B机器上执行同样操作MASTER_HOST改成192.168.1.10。确认复制状态正常SHOW SLAVE STATUS\G重点关注两个字段Slave_IO_Running: YesSlave_SQL_Running: Yes这两个都是Yes说明IO线程和SQL线程都在正常工作。3.4 验证双主数据同步在A机器建一张测试表并插入数据CREATE DATABASE IF NOT EXISTS test_dual_primary; USE test_dual_primary; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; INSERT INTO user (name) VALUES (alice), (bob);在B机器上查询SELECT * FROM test_dual_primary.user; -- 如果能看到alice和bob说明A-B同步正常然后在B机器插入数据回A机器查询在B机器执行INSERT INTO user (name) VALUES (carol);在A机器执行SELECT * FROM test_dual_primary.user; -- 能看到carol说明B-A同步也正常3.5 别忘了配置自增偏移量双主模式下最经典的坑就是自增主键冲突。假设A机器插入一条id1的记录同时B机器也插入一条id1的记录两边各自写成功但同步给对端时就会主键冲突复制线程直接卡住。解决办法是设置自增步长和偏移量# A机器 auto_increment_offset 1 auto_increment_increment 2 # B机器 auto_increment_offset 2 auto_increment_increment 2这样A生成的id永远是1,3,5,7B生成的id永远是2,4,6,8不会冲突。步长2对性能影响微乎其微但换来的是复制稳定非常划算。4. 双主下的故障切换从手动到自动4.1 切换决策的关键指标双主架构中判断一个节点是否真的挂了不能只看进程在不在要看它能否正常响应读写请求。我总结了一套判断维度进程状态mysqld进程是否存活探活超时时间建议3秒TCP端口3306端口能否建立连接查询探活执行SELECT 1是否在2秒内返回复制状态上面的SHOW SLAVE STATUS是否正常这四项都通过才认为节点健康。任何一项不通过都要触发切换流程。但要注意探活组件本身也可能误判比如网络分区时A能连到BB也能连到A但A和B之间的专线断了——这种场景最危险。4.2 基于VIP的快速切换方案最简单的切换方案是配置虚拟IPVIP正常情况下VIP绑定在A机器上应用访问VIP就相当于访问A。A出故障后VIP自动漂移到B应用无感知。我用Keepalived做过这套方案配置核心部分是这样# /etc/keepalived/keepalived.conf vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 50 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1234 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:1 } track_script { chk_mysql } }注意track_script是关键它会在MySQL进程异常时主动降低优先级让VIP漂移过去。脚本内容大致是#!/bin/bash mysqladmin ping -h 127.0.0.1 -u root -pYourPass --silent if [ $? -ne 0 ]; then exit 1 fi这套方案的优点是简单可靠秒级切换不需要改动应用。缺点是脑裂风险如果两台机器之间的心跳网络断了A和B可能同时认为对方挂了同时抢占VIP导致两个节点同时写。4.3 用Orchestrator实现自动故障恢复如果VIP方案满足不了你再往上一步就是用Orchestrator这种专门的MySQL高可用管理工具。它支持自动探测、自动切换、失败恢复而且能记录每一次切换的详细日志。Orchestrator的部署不复杂但配置项很多我建议重点关注这几个DeteectClusterAliasQueries探测集群别名的SQLFailMasterOnDetached主库被移除时是否自动提升新主PreventCrossDataCenterMasterFailover跨机房故障时禁止自动切换避免脑裂我在生产环境里会把跨机房自动切换关掉宁可让机房内单点挂一会儿也不能让两个机房同时写同一份数据。4.4 演练是切换可靠性的唯一保障配置写得再花哨不演练等于白搭。我见过太多团队在故障发生时才发现切换脚本有bug或者VIP漂移没能触发完全是纸面高可用。我的习惯是每隔一段时间做一次故障演练手动停掉一个节点的MySQL服务观察VIP是否漂移、复制是否重连、应用是否正常读写然后恢复该节点确认数据追平后重新加回集群。演练之前先备份别问我怎么知道的演练把自己玩挂的案例真不少。5. 脑裂问题双主架构的最大敌人5.1 什么是脑裂为什么恐怖脑裂发生在两个节点之间网络不通但各自都能对外提供服务的情况下。想象一下A和B之间的心跳断掉A认为B挂了自己接管全部流量B也认为A挂了自己接管全部流量。此时两个节点都在处理写入但互相无法同步数据等网络恢复时两边数据已经产生了分歧而且这个分歧无法自动合并。脑裂是双主架构中最可怕的问题轻则数据不一致重则必须回滚部分数据甚至导致整个集群不可用。很多团队在测试环境完全正常一上线就遇到脑裂根本原因就是没有真正的网络隔离环境去测试。5.2 仲裁节点打破平局的关键解决脑裂的标准手段是引入第三方仲裁。仲裁节点不参与业务读写只负责在心跳失效时投票决定谁是主。常见的实现方式有ZooKeeper/Etcd分布式协调中心记录当前主节点信息节点通过抢锁决定谁当主独立监控节点专门的监控服务器同时探测两个节点发现脑裂时强制执行某一边只读奇数节点方案三个节点投票两个节点达成一致才算合法从根本上避免平局我用ZooKeeper做过一套方案核心逻辑是每个节点启动时尝试创建/ephemeral/master节点创建成功的那个是主节点主节点会定期续约备节点监听这个节点变化一旦发现主节点租约超时就尝试接管。ZooKeeper的ZAB协议本身能保证只有一个节点能创建成功天然避免脑裂。5.3 STONITH对可疑节点做物理隔离仲裁只能决定谁当主但没法阻止一个被判定为故障的节点继续运行。比如A和B网络断了仲裁选了A当主但B并没有真正宕机它还在接受流量这就出问题了。STONITHShoot The Other Node In The Head的思路是仲裁判定某个节点失联后直接把这个节点电源切断或从网络中强制隔离。这样即使它还在尝试运行也无法接受任何外部请求。STONITH需要服务器硬件支持远程管理如IPMI虚拟化环境下也可以用虚拟化平台API直接关闭虚拟机。我用OpenStack环境时就是通过Nova API强制停止故障实例效果非常干净。5.4 应用层也要防脑裂即使底层做了STONITH应用层还是要加防脑裂机制。我常做的一种方式在共享存储或数据库中维护一把分布式锁每个写请求都要先拿到锁才能执行。两个节点抢同一把锁ZooKeeper或Redis分布式锁的强一致语义能够保证同一时刻只有一个节点在写。注意Redis分布式锁本身就有争议在没有RedLock的情况下传统的SETNX锁在网络分区时也可能失效。真要严格防脑裂建议用ZooKeeper或Etcd这种带强一致的协调系统。6. 常见双主架构故障排查与避坑心得6.1 故障排查实录我挑几个真实踩过的坑每一个都是血泪换来的。案例一复制延迟导致切换丢数据某次凌晨大促主库突发大量写入。此时备库复制延迟已经超过5秒但监控没有预警。主库的SSD磁盘故障直接宕机触发了VIP漂移。备库接管后5秒内的数据全部丢失用户投诉炸了锅。事后复盘发现监控只看了复制是否Running没看延迟时间。解决方案监控里加上Seconds_Behind_Master指标超过阈值就告警。同时把主库的binlog同步到独立的日志存储万一真丢数据了还能从binlog回溯。案例二只读没关导致故障切换后写失败有一次手动演练把VIP从A切到B结果业务侧写入直接报错。排查半天发现B机器配置了read_onlyON数据同步时能以SUPER权限绕过但业务账号没有SUPER权限一律被拒。这是在主从架构时代养成的坏习惯备库都设了只读切换时忘了改回来。解决办法是把切换脚本里加上read_only的设置逻辑确保升主后自动关闭只读。案例三半同步复制降级无人察觉MySQL半同步复制在网络抖动时会自动降级为异步复制状态看起来还是正常但实际已经不等待备库确认了。主库突然宕机备库缺的binlog就丢了。这种问题极难排查因为没有明显报错。我现在的做法是监控半同步的状态变量如果降级时间超过1分钟就告警同时在切换脚本里加入一个停止位校验只有当备库的GTID集合覆盖主库时才允许合法切换。6.2 避坑技巧速查表问题类型典型症状排查思路预防措施主键冲突复制SQL线程停止查看Last_SQL_Error字段配置自增偏移量、避免双写数据丢失切换后业务数据不一致对比GTID集合半同步复制延迟监控只读误开切换后写入失败检查read_only变量切换脚本强制处理脑裂两端数据分叉检查心跳和仲裁日志STONITH仲裁节点复制延迟备库数据明显滞后Seconds_Behind_Master读写分离、优化大事务6.3 日常运维的黄金习惯最后分享几条我从实践中总结的习惯虽然看起来基础但真的能救你于水火每次修改配置前都要备份哪怕只是改一个参数。我曾经改错缓冲池大小导致MySQL起不来没有备份只能重建实例耗时两小时教训惨痛。记录每个节点binlog的位置信息用表格或者文档维护。故障时能快速定位到该从哪个位置恢复数据。监控指标别只盯着CPU和内存MySQL的状态变量要重点看Threads_connected、Aborted_connects、Innodb_buffer_pool_read_requests定期做数据校验用pt-table-checksum这类工具检查两个节点的数据是否一致。很多复制问题都是静默发生的不主动校验根本发现不了。切换演练要常态化并且每次演练后写总结。第一次切换可能手忙脚乱第十次就完全驾轻就熟了这里没有捷径。我在实际工作中最大的感受是双主热备架构的技术方案并不难选难的是你真正愿意付出多少精力去保障它。从设计、实施到运维每一个环节都需要投入大量时间和耐心去验证、去演练。如果你正在做这个方向的架构设计希望这篇内容能帮你在关键节点上少走一些弯路尤其是脑裂和故障切换这两个环节一定要想清楚再动手。
返回列表