十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

MySQL主从与主备区别详解:从复制原理到高可用切换实战

MySQL主从与主备区别详解:从复制原理到高可用切换实战 上周帮一个朋友看他们刚上线的系统聊到数据库架构他挺自豪地跟我说我们做了主备两台机器一主一从。我问他从库平时接读流量吗他说不接就搁那儿等着。我又问主库真挂了多久能切过去他说得手动操作熟手大概半小时。我说那你这个既不叫主从也不叫主备准确讲叫有一台机器在待机。这个对话几乎每隔几个月就会重演一次。MySQL 生态里主从和主备这两个词被混用的频率极高高到很多团队的架构文档里写的是一套、实际跑的是另一套。可这两个词背后对应的组件、监控指标、故障处理流程、甚至招人时的岗位要求差别相当大。你要是后端开发搞不清这个做容量规划和故障预案时就会埋雷你要是运维或者 DBA搞不清这个值班时那次切换大概率要翻车你要是正在准备面试这就是道高频且容易露怯的题。我打算把这件事从头到尾说透MySQL 主从复制的底层是怎么跑的主备切换到底依赖哪些额外组件两者在 RTO、RPO 上的差距从哪来再手把手带一套能跑起来的最小架构。内容偏实操参数和命令都能直接抄也会讲几个我在真实环境里踩过的坑。1. 先把概念掰开揉碎主从和主备到底在说啥1.1 一句话区分一个讲数据流向一个讲接管能力我的理解是主从描述的是数据流向关系主备描述的是服务接管关系。主从Master-Slave现在官方术语叫 Master-Replica说的是有一台主库接受写入另外若干台从库通过复制通道把数据同步过去。从库上通常设置read_onlyON但它是活的——你可以连上去查数据、跑报表、做备份、分担读流量。从库存在的首要价值是数据冗余和读扩展。主备Master-Standby说的是有一台主节点对外提供服务另有一台备节点随时准备接管。备节点上跑着同样的数据但它的职责是待命平时通常不承接业务查询或者只承接内部巡检类查询。它存在的首要价值是故障时能顶上关注点是切换需要多久、会丢多少数据。关键差别在这里主备架构里一般包含主从复制或者某种形式的数据同步但主从架构未必具备主备能力。你有一个一主两从的拓扑从库能查能读但主库宕机时没人能自动把其中一个从库扶上位流量也不会自己跑过去——那这套东西就只能叫主从不能叫主备。反过来说任何一套能被称作主备的方案底层一定有一份持续同步的、可接管的数据。打个生活化的比方。主从像是一个主管带两个专员专员能独立处理客户咨询接读请求主管负责签单写请求专员跟着主管学业务同步数据。主备像是长途货车配两个司机一个在开一个在卧铺睡觉车不能停谁累了另一个立刻换手。专员和司机都是备份但一个在分担工作一个在准备接管。注意MySQL 官方从 8.0.23 开始把 Master/Slave 术语替换为 Source/ReplicaCHANGE MASTER TO改成了CHANGE REPLICATION SOURCE TOSHOW SLAVE STATUS改成了SHOW REPLICA STATUS。旧语法在 8.0 里还能用会打 deprecated 警告到 8.4 LTS 就被移除了。写脚本的时候最好直接上新术语省得将来改。1.2 冷备、温备、热备以及为什么大家总把两个词混着说在运维语境里主备其实还有温度之分这个概念是从传统 IT 借过来的放到 MySQL 上依然成立。冷备备机上的数据库根本没在跑只有一份定期备份文件。主挂了你从备份恢复再改配置、改连接串。恢复时间以小时计数据丢失以备份周期计。这种严格说只能叫有备份不叫主备。温备备机上的数据库跑着复制通道是通的数据基本实时但不接受自动切换。主库挂了需要人工确认、人工提升、人工改流量。这是绝大多数中小团队实际的状态。热备备机同步着故障检测、选主、流量切换全自动业务方基本无感最多丢一两秒的连接。这是真正的高可用方案成本也最高。为什么两个词老被混用因为大部分自称做了主备的团队做的其实是一主一从 一份写得很详细的手工切换手册。从拓扑图上看像个主备从数据流上看就是主从从故障处理上看是个人肉温备。这不丢人很多业务根本不需要热备花那套成本纯属浪费。问题在于你要清楚自己在哪一档别拿着温备的底子去承诺热备的 SLA。跨领域看这套思想是通用的。我见过的一些其他领域案例——比如双机通信里的主从信号、设备电源的主备切换电路、两台控制节点的主备关系、工业产线上的主从控制——本质都是同一件事一个节点主导另一个跟随跟随者要么分担负载要么随时接管。区别只在切换的触发条件和接管速度。落到 MySQL 上就变成了复制机制与高可用切换机制这两件事。判断标准我给三个问题你回答完就知道自己在哪一档了备机平时对外提供读吗故障时流量能自动切过去吗切换窗口期能容忍丢多少数据第一个问题答是偏主从后两个问题答案越快、丢得越少就越接近主备。1.3 用 RTO、RPO、读扩展这三把尺子量清楚谈架构不谈指标就是耍流氓衡量主从和主备差异的核心指标就三个。指标含义主从架构典型值主备架构典型值优化手段RTO恢复时间目标从故障发生到服务恢复的耗时人工介入10 分钟到数小时自动切换10 秒到 60 秒故障检测组件、VIP 漂移、中间件、连接池重连RPO数据恢复点目标故障时允许丢失的数据量异步复制可能丢一个事务组半同步可做到几乎不丢rpl_semi_sync_source_wait_pointAFTER_SYNC、MGR 多数派提交读扩展能力能否用冗余节点分担读流量天然支持加从库即可一般不用于扩展中间件读写分离、从库权重配置成本资源与运维投入低一到两台机器高至少三节点 管理组件按业务等级分级部署RTO 这一栏值得多说两句。人工切换的十分钟到数小时不是吓唬人。真实场景里你需要在几分钟内完成确认主库确实挂了而不是网络抖动、确认哪个从库数据最新、确认 GTID 差集、执行提升、切换 VIP、通知应用方重启连接池、观察新主负载。任何一步卡住半小时就过去了。RPO 这一栏是很多人的认知盲区。异步复制下主库把事务提交完就返回给客户端了从库才慢慢收 binlog。主库在提交后、binlog 传到从库前崩溃这部分数据就永久消失。半同步复制从 5.7 开始默认用AFTER_SYNC模式主库会等至少一个从库确认收到 binlog 之后再向客户端返回提交成功这样客户端看到提交成功的数据一定存在于至少两个节点上。代价是每次写入多一个网络往返延迟增加大概 0.1 到 0.5 毫秒正常网络下可以接受。还有个容易被忽略的点主备方案至少要三个节点。两节点的主备在脑裂场景下无法仲裁——网络一分区两边都以为对方挂了都把自己当主双写就来了。加一个轻量仲裁节点不存数据只投票或者引入外部一致性组件才能解决这个问题。这部分在第三章详解。2. 主从复制的底层机制一条 binlog 的旅程2.1 binlog、relay log 和三个线程的分工要理解主备切换为什么难得先理解主从复制是怎么跑起来的。整条链路上有三个线程在配合。主库侧有一个dump 线程binlog_dump_thread。当从库连上来主库为它创建一个 dump 线程负责从指定的 binlog 位置开始把 binlog event 推给从库。注意是推不是拉主库是主动方。从库侧有两个线程。IO 线程负责连主库、接收 binlog event、写进本地的 relay log并记录已经拉到了哪个位置。SQL 线程8.0 之后官方叫 applier协调线程负责读 relay log解析出实际的变更然后应用到存储引擎里。写路径上的顺序也很关键。一个事务在提交时InnoDB 先写 redo logprepare 状态再写 binlog最后把 redo log 置为 commit 状态——这是经典的两阶段提交目的是保证 binlog 和 redo log 的一致性也是主从复制能对上的基础。关键参数我在下面这张表里列全了配置的时候逐项核对参数建议值说明server_id全局唯一整数同一个复制拓扑里绝对不能重复重复会导致复制环路log_binON主库必须开从库建议也开级联复制和切换时需要binlog_formatROW生产环境不要用 STATEMENT函数、LIMIT、不确定排序都会导致主从不一致binlog_row_imageFULLMINIMAL能减小日志量但某些场景会影响复制正确性别轻易改gtid_modeON新部署直接开切换和一致性校验都靠它enforce_gtid_consistencyON与 GTID 配套必须一起开read_only/super_read_only从库都开只开read_only拦不住有 SUPER 权限的账号sync_binlog1每次提交刷盘性能换安全金融类业务必开innodb_flush_log_at_trx_commit1同上配合sync_binlog1才是真正的不丢数据relay_log_purgeON自动清理已应用的 relay log防止磁盘打满log_replica_updatesON从库也写自己的 binlog做级联复制和切换必备为什么我坚持推荐ROW格式举个实际例子UPDATE t SET updated_at NOW() WHERE id 100。STATEMENT 格式下binlog 记录的是这条 SQL 原文从库重放时NOW()返回的是重放时刻的时间和主库不一致。类似的还有UUID()、RAND()、以及没有ORDER BY的LIMIT语句。ROW 格式记录的是每一行变更前后的具体值从库照着改就行不依赖任何执行环境。代价是日志体积大一条更新十万行的语句ROW 格式的 binlog 可能是几百 MB。这就引出了后面要讲的大事务问题。2.2 异步、半同步、组复制三种模式怎么选复制模式决定了主库在什么时刻向客户端返回提交成功直接决定了 RPO。异步复制是默认模式。主库提交事务写完 binlog 就返回完全不等从库。性能最好吞吐最高但主库崩溃时未传出去的 binlog 对应的数据就丢了。适合日志类、统计类、能接受少量数据丢失的业务。半同步复制需要装插件。在 MySQL 8.0.26 之前插件叫rpl_semi_sync_master和rpl_semi_sync_slave8.0.26 之后改名为rpl_semi_sync_source和rpl_semi_sync_replica旧名字还保留为别名。它的逻辑是主库提交事务时至少等待一个从库确认收到 binlog才向客户端返回成功。这里有个非常重要的参数演进rpl_semi_sync_source_wait_point。老版本默认是AFTER_COMMIT——主库先提交事务再等从库 ACK。这有个致命问题主库提交后崩溃客户端已经收到成功响应但从库还没收到 binlog切换过去之后这部分数据就凭空消失了客户端会觉得我明明写成功了。从 5.7.2 开始引入AFTER_SYNC模式并成为默认值——主库先等 ACK收到后再提交给客户端。这就把客户端看到成功和数据至少在两处存在绑定在了一起。新部署一律用AFTER_SYNC没有例外。还有个参数rpl_semi_sync_source_timeout默认 10000 毫秒。含义是等从库 ACK 超过这个时间主库自动退化成异步模式继续服务避免整个集群被一个卡住的从库拖死。这个值是双刃剑——设太小网络一抖就退化你的半同步形同虚设设太大从库真挂了主库写入会被阻塞。我的经验是 1000 到 3000 毫秒同时配上监控告警一旦Rpl_semi_sync_source_status变成 OFF 立刻通知。**组复制MGR**是官方的高可用方案基于 Paxos 协议。事务需要在多数派节点上达成一致才能提交因此天然不会丢数据。单主模式下只有一个节点可写主节点故障后自动选举新主配合 MySQL Router 可以实现对应用透明的自动切换。代价是配置复杂、对表结构有硬性要求每张表必须有主键、sql_require_primary_keyON、外键支持有限、网络延迟敏感。跨机房部署时 Paxos 的往返延迟会直接吃掉写入性能这点必须提前算清楚。三种模式的横向对比如下维度异步复制半同步复制组复制 MGR数据一致性可能丢失最近事务至少一个从库有数据多数派一致不丢写入延迟最低增加一个网络往返明显增加受网络影响大自动选主不支持不支持支持配置复杂度低中高表结构限制无无必须有主键外键受限适用场景日志、报表、可容忍丢失大多数在线业务核心交易、要求零丢失我的实际组合经验是一主两从 半同步 GTID 中间件做切换这个组合在成本和可靠性上平衡得最好。两个从库的意义在于半同步只要求一个从库 ACK挂掉一个从库时不会退化还有缓冲时间处理。2.3 GTID 与并行复制故障切换的两个前置条件**GTID全局事务标识**是主备切换能自动化的前提。每个事务在主库上提交时会被分配一个全局唯一的 GTID格式是source_id:sequence_number。带来的好处很直接第一切换时不用再算 binlog 文件名和偏移量。以前你要SHOW MASTER STATUS拿到mysql-bin.000123和位置 456789再让从库CHANGE MASTER TO MASTER_LOG_FILE..., MASTER_LOG_POS...一旦算错就复制断裂。有了 GTID从库直接SOURCE_AUTO_POSITION1它会自己跟主库协商从哪个位置开始补。第二判断数据差异变得简单。比较两个节点的gtid_executed差集就是缺的事务一眼就能看出来。切换前确认候选新主的gtid_executed是否包含旧主的全部是保证不丢数据的关键动作。第三可以从任意从库接任意位置。级联复制、拓扑调整、新增从库全部简化。开启 GTID 有几个注意点必须在所有节点同时开启gtid_modeON和enforce_gtid_consistencyON且开启过程需要按OFF - OFF_PERMISSIVE - ON_PERMISSIVE - ON的顺序滚动不能直接跳。另外绝对不要在从库上做本地写入会造成 GTID 冲突复制直接卡死。并行复制解决的是从库回放速度问题。MySQL 5.6 之前从库只有一个 SQL 线程串行重放主库并发写入再高也没用从库必然落后。5.6 引入了基于库DATABASE的并行粒度太粗单库多表场景完全无效。5.7 引入LOGICAL_CLOCK基于主库的组提交信息判断哪些事务可以并行重放效果提升明显。8.0 进一步引入 writeset 级别的依赖检测binlog_transaction_dependency_trackingWRITESET能识别出实际不冲突的事务并行度更高。配置就两行replica_parallel_workers 8 replica_parallel_type LOGICAL_CLOCK replica_preserve_commit_order ONreplica_parallel_workers建议设成 CPU 核数的 2 到 4 倍但别超过 16线程切换开销会反噬。replica_preserve_commit_orderON必须开否则从库上事务的提交顺序可能和主库不一致对于依赖提交顺序的业务逻辑比如用时间戳做的增量拉取会产生数据错乱。还有一个参数binlog_group_commit_sync_delay在主库上人为拉长组提交窗口让更多事务进入同一组从而提升从库并行度——本质是用主库的写入延迟换从库的回放速度只有从库延迟成为瓶颈时才考虑动它。顺带说一句 8.0.26 之后的命名变化slave_parallel_workers改成了replica_parallel_workersslave_preserve_commit_order改成了replica_preserve_commit_order。写新配置直接用新名字。3. 主备架构的关键组件切换是怎么自动发生的3.1 故障检测从心跳到多数派投票主从到主备中间缺的那一环就是故障检测。没有它再好的复制拓扑也只是一个静态的冗余结构。最基础的做法是心跳探测。管理组件每隔一两秒向每个节点发一次检查可能是SELECT 1可能是 ping 端口也可能是检查复制线程状态。连续失败 N 次就判定节点异常。这里的 N 特别关键——设成 1一次网络抖动或一次长 GC 就触发切换误切比不切更糟设成 10故障发现要 20 秒RTO 直接翻倍。我的经验值是连续 3 次、间隔 1 秒同时给探测加上超时控制。但心跳解决不了脑裂。网络分区时管理组件连不上主库它以为主库死了而主库自己还活着还在接受写入。如果这时把一个从库扶上位两个可写节点同时存在数据就分叉了。老主恢复后两边数据已经没法自动合并。解决思路是引入多数派概念。组复制靠 Paxos 自动做到这一点少于半数节点存活时集群自动变成只读不选举新主。对于传统主从架构需要额外机制第一加仲裁节点。比如 keepalived 场景下部署第三个轻量节点做见证者或者在两个数据节点之外再放一个小规格实例专门用于投票。奇数个节点才有意义。第二引入外部一致性存储。Orchestrator 的 raft 模式依赖 etcd 或 consul 做元数据一致性避免管理组件单点误判。第三做 fencing隔离。判定老主异常后主动切断它的网络或者强制关闭它的数据库进程确保它不可能再接受写入。这个动作在专业术语里叫 STONITH听起来吓人实际就是iptables封端口或者调用云 API 关机。第四用super_read_only兜底。老主恢复后如果发现自己不是主立刻置为只读。这个配合前三条用是最后一道防线。3.2 选主与数据补偿新主凭什么不丢数据判定主库真挂了之后下一个问题是从库有好几个选谁上位。选主逻辑一般按这几层优先级排序同机房优先于跨机房网络延迟决定复制延迟复制延迟最小的优先GTID 差集最小的优先配置规格和版本一致的优先。MHA 和 Orchestrator 都实现了类似的策略也支持人工指定候选优先级列表。比选主更重要的是数据补偿。假设旧主的gtid_executed到最后是uuid:1-1000候选新主是uuid:1-980中间差 20 个事务。这 20 个事务如果只存在于旧主的 binlog 里旧主又起不来了那这 20 个事务对应的数据就真丢了。RPO 不为零。这里的分野在于复制模式。如果用的是半同步且等待点是AFTER_SYNC那么凡是客户端收到提交成功的事务至少有一个从库已经收到候选新主理论上不会有差集。如果用的是异步复制差集就可能存在MHA 这类工具会尝试从其他从库的 relay log 里找出缺失的事务并补上——这个叫差异补偿。但补偿的前提是别的从库有这些数据如果只有一个从库那就没得补。注意确认差集这一步不能省。我见过为了把 RTO 压到 10 秒内直接跳过 GTID 比对强行提升的案例事后发现丢了大概 300 条订单。业务方花了两天人工核对补数据总成本远高于多等那 20 秒。宁可多花时间确认也不要盲目提升。旧主恢复回来之后的处理也容易被忽略。它上面可能有新主没有的、未提交或已提交未同步的事务。标准做法是确认它是只读、把它按新从库的方式重新挂到新主上、用mysqldump或克隆插件重建数据而不是直接START REPLICA就完事。数据已经分叉的情况下强行接回去复制会报错或者更糟——静默产生不一致。3.3 流量切换VIP、中间件和 DNS 三种打法新主选出来了数据也确认了接下来要让应用的流量真的跑过去。这一步的细节决定了你的切换是看起来成功还是真的成功。VIP 漂移是最常见的方式。应用连一个虚拟 IP主库正常时这个 VIP 在主节点上切换时通过 ARP 广播把 VIP 迁移到新主节点。keepalived 是典型实现。优点是应用侧完全不用改配置缺点有三个一是旧连接问题应用连接池里还握着指向老 IP 的 TCP 连接VIP 漂走了这些连接就废了必须等应用侧感知超时并重建二是 ARP 缓存同网段的设备可能还缓存着旧的 MAC 地址映射需要主动发免费 ARP三是跨网段不好使VIP 一般只能在同二层网络内漂。中间件方案更现代。应用连 ProxySQL 或 MaxScale由中间件维护到后端各节点的连接。中间件自己能感知后端拓扑变化配合 Orchestrator 之类的管理组件故障时自动把写流量指向新主、读流量分给从库。好处是应用完全无感还能顺便做读写分离、连接复用、查询缓存、慢查询拦截。代价是多了一层组件要维护多一跳网络延迟中间件本身也得做高可用。我比较推荐这个方案因为读写分离这个能力本身就能把主库压力降下来一举两得。DNS 切换在云环境里很常见。把域名解析改到新主的地址靠 DNS 的 TTL 控制生效速度。问题是 DNS 有缓存客户端和各级解析器都可能缓存旧记录TTL 设成 10 秒也可能实际几分钟才生效不可控因素太多。适合对 RTO 要求不高的场景。方式应用改造切换速度主要风险适用场景VIP 漂移无秒级旧连接、ARP 缓存、跨网段限制同机房、传统架构中间件连接地址变更一次秒级中间件自身高可用需要读写分离的在线业务DNS 切换无秒到分钟DNS 缓存不可控云环境、容灾切换应用服务发现需要接入 SDK秒级改造成本高微服务架构应用侧该做的配合工作同样重要。连接池务必配置maxLifetime建议小于数据库wait_timeout保证连接定期重建配置连接有效性检测如 HikariCP 的connectionTestQuery或 JDBC4 的isValid关闭autoReconnect这类会掩盖故障的参数配置合理的连接超时建议 3 到 5 秒和重试次数。切换发生时应用会有一批请求失败这是正常的关键是要快速失败、快速重连而不是卡死几十秒。3.4 主流方案横向对比别选最火的选最合适的把常见的几套方案摆在一起看选型会清楚很多。方案维护状态GTID 支持自动切换数据补偿复杂度说明MHA基本停更支持支持有中5.7 时代主流现在新项目不建议Orchestrator活跃支持支持弱靠半同步中高拓扑可视化强支持 raft 模式防脑裂MGR Router官方维护强制支持不丢高官方方案网络要求高ProxySQL 脚本活跃支持需自研视脚本而定中灵活可控适合有研发能力的团队云 RDS 高可用版云厂商维护支持支持通常不丢低省心但要接受黑盒和成本几个选型上的实话。MHA 是个好工具但项目基本不再更新在新版本 MySQL 上会遇到兼容问题新项目别选。Orchestrator 功能强拓扑发现和可视化做得漂亮但要配好 raft 模式才有防脑裂能力不然管理组件自己就是单点。MGR 是官方路线零丢失这个特性很吸引人但跨机房部署时 Paxos 的延迟会让人很痛苦同城三机房是它的舒适区。自研脚本不是不能做很多团队就靠一个几百行的 Python 脚本加 keepalived 跑了好几年前提是你得把边界条件都覆盖到而且要有测试。中小团队如果预算有限我的建议是半同步 两个从库 keepalived VIP 一个定时探测脚本先跑起来把流程跑熟。等业务量上来了再考虑中间件和更复杂的方案。架构是演进的不是一次设计到位的。4. 手把手搭一套最小可用的主从加半自动主备切换4.1 实验环境准备三台机器和一份能直接抄的配置文件我用 Docker 起三个 MySQL 8.0 实例做实验这样一台开发机就能跑成本最低。生产上换成三台独立机器配置逻辑完全一样。# docker-compose.yml services: mysql-master: image: mysql:8.0.36 container_name: mysql-master environment: MYSQL_ROOT_PASSWORD: Root_2024#Strong TZ: Asia/Shanghai ports: - 3311:3306 volumes: - ./master/conf:/etc/mysql/conf.d - ./master/data:/var/lib/mysql command: --default-authentication-pluginmysql_native_password mysql-replica1: image: mysql:8.0.36 container_name: mysql-replica1 environment: MYSQL_ROOT_PASSWORD: Root_2024#Strong TZ: Asia/Shanghai ports: - 3312:3306 volumes: - ./replica1/conf:/etc/mysql/conf.d - ./replica1/data:/var/lib/mysql mysql-replica2: image: mysql:8.0.36 container_name: mysql-replica2 environment: MYSQL_ROOT_PASSWORD: Root_2024#Strong TZ: Asia/Shanghai ports: - 3313:3306 volumes: - ./replica2/conf:/etc/mysql/conf.d - ./replica2/data:/var/lib/mysql三个实例的配置文件分开放主库的my.cnf[mysqld] server_id 11 log_bin /var/lib/mysql/binlog binlog_format ROW binlog_row_image FULL binlog_expire_logs_seconds 604800 sync_binlog 1 innodb_flush_log_at_trx_commit 1 gtid_mode ON enforce_gtid_consistency ON log_replica_updates ON max_connections 500 character_set_server utf8mb4 collation_server utf8mb4_0900_ai_ci transaction_isolation READ-COMMITTED # 半同步主库插件 plugin_load_add rpl_semi_sync_sourcerpl_semi_sync_source.so rpl_semi_sync_source_enabled ON rpl_semi_sync_source_timeout 2000 rpl_semi_sync_source_wait_point AFTER_SYNC从库配置在主库基础上改三处server_id改成 12 和 13加上只读把半同步插件换成 replica 版本server_id 12 read_only ON super_read_only ON relay_log /var/lib/mysql/relaylog relay_log_purge ON replica_parallel_workers 8 replica_parallel_type LOGICAL_CLOCK replica_preserve_commit_order ON plugin_load_add rpl_semi_sync_replicarpl_semi_sync_replica.so rpl_semi_sync_replica_enabled ON逐项说下为什么。transaction_isolation设成READ-COMMITTED主要是为了减少间隙锁配合 ROW 格式的 binlog 不会有一致性问题并发写入场景下收益明显。binlog_expire_logs_seconds设 7 天太短会导致从库落后太多时 binlog 已被清理、复制断裂需要重建太长会撑爆磁盘。replica_parallel_workers8在 8 核机器上够用配合LOGICAL_CLOCK能覆盖绝大多数场景。半同步的timeout设 2000 毫秒是我在延迟抖动和阻塞风险之间找的平衡点。super_read_only单独说一句只设read_onlyON时拥有 SUPER 权限的账号比如 root仍然可以写入。生产环境里经常出现有人在从库上执行了运维 SQL 造成数据不一致加上super_read_only才能彻底堵住。切换时记得把它关掉否则新主没法写。4.2 建立主从复制从备份到 START REPLICA 的完整命令环境起来之后第一步是在主库上创建复制账号。CREATE USER repl% IDENTIFIED BY Repl_2024#Strong; GRANT REPLICATION SLAVE ON *.* TO repl%; FLUSH PRIVILEGES;生产环境把%换成具体的网段比如192.168.1.%最小化权限暴露面。密码策略如果开了validate_password简单密码会被拒绝。第二步做一次全量备份用mysqldump就够数据量大或者对一致性要求高的话用 Percona XtraBackup 或者 MySQL Shell 的 clone 插件。mysqldump -h127.0.0.1 -P3311 -uroot -pRoot_2024#Strong \ --single-transaction \ --master-data2 \ --set-gtid-purgedON \ --routines --triggers --events \ --databases app_db \ /tmp/app_db_full.sql--single-transaction保证 InnoDB 表在备份期间的数据一致性不会锁表但备份期间不能有 DDL。--set-gtid-purgedON会把当前主库的 GTID 信息写进备份文件从库导入后就知道缺哪些事务。备份文件里会有一行注释记录着CHANGE MASTER TO需要的位点信息GTID 模式下通常用不上但留着没坏处。第三步把备份恢复到两个从库上然后建立复制关系。MySQL 8.0.23 之后用新语法STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST mysql-master, SOURCE_PORT 3306, SOURCE_USER repl, SOURCE_PASSWORD Repl_2024#Strong, SOURCE_AUTO_POSITION 1, SOURCE_CONNECT_RETRY 10, SOURCE_RETRY_COUNT 3, GET_SOURCE_PUBLIC_KEY 1; START REPLICA;SOURCE_AUTO_POSITION1是 GTID 模式的关键让它自动协商起始位置。GET_SOURCE_PUBLIC_KEY1是为了兼容 MySQL 8 默认的caching_sha2_password认证插件——复制账号如果用的是这个插件从库在非 SSL 连接下需要向主库索要公钥来加密密码不加这个参数会报Authentication plugin caching_sha2_password cannot be loaded。两个从库都执行一遍注意SOURCE_HOST可以用容器名也可以用 IP看你的网络配置。4.3 复制状态与延迟的正确观测方式搭完之后第一件事是确认复制真的跑起来了。SHOW REPLICA STATUS\G关键字段的解读我整理成一张表字段期望值异常含义Replica_IO_RunningYesNo 表示连不上主库或认证失败Replica_SQL_RunningYesNo 表示回放出错通常是主键冲突或数据不一致Seconds_Behind_Source接近 0只是粗略值大事务和并行复制下不准Retrieved_Gtid_Set持续增长IO 线程已拉取的 GTID 集合Executed_Gtid_Set追上主库SQL 线程已应用的 GTID 集合Last_IO_Error空有内容就是连接层报错Last_SQL_Error空有内容就是回放层报错Auto_Position10 表示用的是文件位点模式Seconds_Behind_Source这个值我不太信。它的计算方式是从库当前时间减去正在回放的事务在主库上产生的时间戳遇到大事务时事务刚开始回放时它显示几百秒回放快结束时还是几百秒实际上早就追上了虚惊一场。8.0 之后我更推荐用 performance_schema 观测SELECT WORKER_ID, SERVICE_STATE, APPLYING_TRANSACTION, LAST_APPLIED_TRANSACTION, IF(APPLYING_TRANSACTION IS NULL, idle, TIMESTAMPDIFF(MICROSECOND, APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP, NOW())/1000000) AS applying_lag_seconds FROM performance_schema.replication_applier_status_by_worker ORDER BY WORKER_ID;这个查询能看每个 worker 线程的实时状态和落后秒数精确到微秒比Seconds_Behind_Source靠谱得多。数据一致性校验用 Percona Toolkit 的pt-table-checksum它会在主库上分块计算校验和通过复制传到从库比对pt-table-checksum \ --host127.0.0.1 --port3311 \ --userroot --passwordRoot_2024#Strong \ --databasesapp_db \ --replicatepercona.checksums \ --no-check-binlog-format \ --chunk-time0.5--no-check-binlog-format在 ROW 格式下需要加否则工具会因为检测到 ROW 格式而拒绝执行。--chunk-time0.5控制每个分块的处理时间不超过 0.5 秒避免对主库造成压力。这个工具务必在业务低峰期跑虽然它做了限速但对大表还是有不小的影响。4.4 半自动切换演练一次不丢数据的手动接管自动切换工具的搭建成本高但切换的流程你必须走一遍哪怕先用手动执行。我把标准流程拆成六步每一步都有它的道理。第一步确认主库真的挂了。别急着动手先用多个渠道确认从两个从库分别 ping 主库、telnet 主库端口、看一下管理网络能不能通。这一步是防误判网络抖动导致的假故障占了误切换的相当比例。第二步在旧主库上尽最大可能锁写。如果旧主还能连上执行SET GLOBAL super_read_onlyON;。如果连不上跳过。第三步选新主。对比两个从库的gtid_executedSELECT global.gtid_executed;选包含事务更多的那个。如果两个一样选延迟更小、规格更高的那个。第四步确认新主已经追平。在新主上执行SHOW REPLICA STATUS确认Replica_SQL_RunningYes且Retrieved_Gtid_Set和Executed_Gtid_Set相等——也就是拉到的都应用完了。第五步提升新主STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only OFF; SET GLOBAL super_read_only OFF;RESET REPLICA ALL会清空复制元数据让它彻底脱离从库身份。这一步做完新主就可以接受写入了。第六步把另一个从库指向新主STOP REPLICA; CHANGE REPLICATION SOURCE TO SOURCE_HOST 新主地址, SOURCE_PORT 3306, SOURCE_USER repl, SOURCE_PASSWORD Repl_2024#Strong, SOURCE_AUTO_POSITION 1; START REPLICA;然后切换 VIPkeepalived场景下停掉旧主的 keepalived 进程新主的会自动接管通知应用方重建连接池。整个流程熟练之后五分钟能走完。这也是为什么我一直强调要演练——不是因为流程复杂而是因为真出事的时候人会慌只有肌肉记忆能救你。我建议每个季度做一次完整的切换演练在预发环境把演练过程和耗时记录下来逐步优化。提示切换演练一定要包含旧主恢复这个环节。很多人演练到新主接管就结束了结果真实故障时旧主恢复回来没人知道该怎么处理。正确做法是把旧主按从库重建用 clone 插件或者重新导入备份而不是直接START REPLICA。5. 踩坑实录主从和主备最常见的那些问题5.1 复制中断类问题速查表复制断裂是日常运维里出现频率最高的问题我把常见错误码和处置方式整理出来值班的时候可以对着查。错误码报错信息关键词常见原因处置方式1062Duplicate entry从库被本地写入过或双主环路写入确认数据后SET GLOBAL SQL_SLAVE_SKIP_COUNTER1跳过或重建从库1032Could not find record主从表结构不一致或从库数据被删改用pt-table-sync修复差异行或直接重建1236Got fatal error 1236binlog 已被清理、GTID 不连续重新做全量备份并重建复制1045Access denied密码错、账号不存在、认证插件不匹配重建复制账号注意GET_SOURCE_PUBLIC_KEY1594Relay log read failurerelay log 文件损坏通常是磁盘满或异常关机STOP REPLICA; RESET REPLICA; START REPLICA重新拉取2003Cant connect to MySQL server网络不通、防火墙、主库端口未监听逐层排查网络和主库状态1872Slave can not handle replication eventsrelay log 起始位置不对用 GTID 模式重建-从库进程被 OOM Killer 杀掉buffer pool 配置过大实例内存超限调小innodb_buffer_pool_size加监控除了错误码还有几种不报错但有问题的情况更隐蔽。半同步静默退化。从库超过 timeout 没响应主库会自动退化成异步但不报错只在Rpl_semi_sync_source_status这个状态变量里体现为 OFF。如果你没监控这个值可能连续跑了好几天异步复制都不知道。我的做法是在监控系统里专门加一条规则这个值一旦变成 OFF 立刻告警。复制延迟悄悄涨上去。没有报错就是Seconds_Behind_Source慢慢从 0 变成 100、500、3000。常见诱因是大事务——比如一条DELETE FROM logs WHERE created_at 2024-01-01删掉两百万行在主库上执行了五分钟binlog 传到从库后从库的 SQL 线程也要重放五分钟这五分钟内延迟一直在涨。解决办法是把大事务拆小改成循环批量删除每批一千到五千行中间 sleep 几十毫秒。从库磁盘写满导致复制中断。relay log 加上 binlog 加上数据文件从库的磁盘占用通常比主库还高。监控磁盘水位要盯紧relay_log_purgeON只是自动清理已应用的如果回放卡住relay log 会一直堆积。5.2 切换翻车类问题速查表切换相关的问题往往后果更严重我按发生概率从高到低排。现象根本原因预防手段双主同时可写数据分叉脑裂缺乏多数派仲裁三节点仲裁、fencing、super_read_only兜底VIP 漂了但应用连不上连接池持有旧连接、ARP 缓存未刷新应用侧配maxLifetime、切换后主动清缓存切换后新主瞬间被打满从库规格低或切换后既要写又要承担全部读新主规格不低于旧主切换后延迟放宽读路由老主恢复后重复写入没有做隔离老主自己恢复成可写状态STONITH 或启动时强制置为只读自增主键冲突双主架构下auto_increment配置未错开设auto_increment_increment和auto_increment_offset切换后延迟暴涨新主积压了旧主未同步的事务切换前确认 GTID 已追平触发器或存储过程重复执行STATEMENT 格式下的副作用用 ROW 格式从库不执行触发器逻辑脑裂这条单独展开说。它的可怕之处在于两个节点都认为自己是主都接受写入等发现的时候数据已经分叉而且没法自动合并。判断脑裂的标志是两个节点的gtid_executed出现了对方没有的事务。这时候唯一的处理方式是人工介入决定保留哪一侧的数据把另一侧重建。损失的数据量取决于发现得有多晚。预防上除了前面说的仲裁和 fencing还有一个简单有效的习惯切换脚本里第一步永远是确认旧主不可写。不管是封 IP、关进程还是发关机指令先做了这件事再考虑提升新主。顺序不能颠倒。自增主键冲突在双主架构里很常见。假设两个节点都从 1 开始自增同时插入两边都会生成 id1复制过去必然冲突。解决办法是错开-- 节点 A SET GLOBAL auto_increment_increment 2; SET GLOBAL auto_increment_offset 1; -- 节点 B SET GLOBAL auto_increment_increment 2; SET GLOBAL auto_increment_offset 2;这样 A 生成 1、3、5、7B 生成 2、4、6、8不会撞车。不过说实话双主架构我一般不推荐除非有明确的写入本地化的需求否则一主多从加自动切换更简单可靠。5.3 几个反直觉的经验最后分享几个和直觉相反的观察都是我实际踩过之后才明白的。从库不是越多越好。每个从库在主库上都对应一个 dump 线程都会消耗网络带宽和 CPU。从库数量从 3 加到 10主库的 binlog 发送压力是线性上涨的而你的读扩展收益可能已经边际递减了。更麻烦的是从库多了之后任何一个从库出问题都会触发告警值班噪音大增。我的建议是够用就好一般业务 2 到 3 个从库足够读写分离之外的需求报表、备份、数据同步用专门的从库别和在线流量混在一起。表结构不一致时复制不一定报错。这是 ROW 格式的一个副作用。主库表有 5 列从库表有 6 列多了一个有默认值的列复制照样能跑通。或者主库某列是INT从库是BIGINT也可能静默通过。等到某一天真的要切换了才发现两边结构不一致新主上的应用行为可能和旧主不一样。所以 DDL 必须走统一的发布流程禁止任何人在从库上手动改表结构。定期用mysqlshell util.checkConsistency或者pt-table-checksum做校验能提前发现这类问题。时间字段带来的偏差容易被忽略。TIMESTAMP类型在存储时会转成 UTC读取时按会话时区转换。如果主从的time_zone设置不一致同一个数据在不同节点上读出来的显示值是不一样的。更隐蔽的是NOW()、CURRENT_TIMESTAMP这类函数在主从上的行为——ROW 格式下记录的是实际值所以数据本身是同步的但如果你的业务逻辑依赖数据库时间做判断两边的时间基准不一致就会出问题。统一用 UTC 存储、在应用层做时区转换是最干净的做法。备份和主从不是一回事。我见过团队觉得我有从库了不需要备份。从库能防硬件故障但防不了逻辑错误——有人在主库上执行了DELETE FROM users且没有WHERE这个操作会瞬间同步到所有从库你手里就什么都没有了。从库是冗余不是备份。备份要独立要异地要定期做恢复演练。延迟从库SOURCE_DELAY是一个不错的折中方案让从库故意延迟一小时应用误删之后还有抢救窗口CHANGE REPLICATION SOURCE TO SOURCE_DELAY 3600;这一个小时的延迟在误操作场景下价值千金。监控复制状态不能只看线程是否 Yes。Replica_IO_RunningYes和Replica_SQL_RunningYes只说明线程活着不代表数据同步正常。真正要监控的是 GTID 差集、每个 worker 的落后时间、以及复制延迟的趋势。趋势比瞬时值重要得多——延迟从 0 缓慢爬到 600 秒说明有持续性的问题在积累比瞬间跳到 600 秒然后回落更值得警惕。我个人在实际操作中的体会是主从和主备这两件事技术难度其实不高难的是把边界情况想全、把流程跑熟、把监控做实。工具能帮你做切换但判断什么时候该切、切完怎么处理旧主、怎么跟业务方沟通这些还得靠人。平时多演练几次真出事的时候就不会手忙脚乱。
返回列表