容灾的底牌:从日志复制到脑裂仲裁的工程博弈

容灾?我见过太多老板拍桌子:“不是双机热备吗?怎么还丢了20分钟数据?” 这话听着来气,但折射出行业普遍认知的荒谬。备份不是容灾,复制也不是备份。今天我不讲那些云厂商宣传册上的废话,直接从日志复制协议和脑裂仲裁这两个底层机制拆开聊。你最好做好准备,因为接下来我会先说结论再推导过程——不习惯的可以关页面了。

复制不是拷贝,是状态机的同步游戏

你以为容灾就是把数据多放几个地方?太天真了。核心问题是你到底想把“哪个时刻”的样子保存下来。同步复制、异步复制、半同步,玩的全是状态机对日志的消费节奏。

状态机是啥?你可以把每台机器看成一个人在反复执行同一个剧本,剧本里的每一行字就是一条日志。只要所有人按同样顺序执行同样的行,最终状态必然一致。同步复制的意思就是,这台机器念一行,必须等那台机器也念完了,才觉得这事过去了。异步呢?只管自己念,别人爱听不听,回头有空再补。

用日常来类比:同步复制是两人接力赛,必须交接棒才松手;异步复制是发快递,扔给快递员就走。但快递会掉件,也会晚点。半同步呢,你只要求快递员回复你“已揽收”,不一定必须送到目的地。可你知不知道,快递员让你以为揽收了,转运途中可能把包裹丢了?这就是半同步的尴尬。

一个关键推导:为什么同步复制RPO=0?因为主库向客户端返回“提交成功”之前,必须确认从库已经在日志里落盘了。也就是说,客户端看到的每一笔交易,至少存在于两个独立节点上。之后哪怕主库被车撞了,从库的日志依然完整。但注意,RPO=0不是绝对——如果主从同时被泥石流埋了,那照样归零。所以容灾是概率学,不是数学。我们只能把“单点故障”的概率压到无限小,却收拾不了机房全体断电这种灾难。

这里还藏着个很多人忽略的魔鬼:日志的连续性。主库的binlog是连续编号的,从库按序号应用。同步复制要求每个序号都得到确认;但Raft里的实现是只确认到某个索引,中间日志都算已复制。两相对照,你会发现Raft的机制更接近“批量确认”,能显著降低同步开销。这就是为什么现代分布式数据库偏爱Raft当“变速箱”,而不是老式的线程间通信。

分布式系统Raft日志复制时序图
分布式系统Raft日志复制时序图

半同步呢?折中。主库只等一个从库确认就算成功,其余从库异步追。这里有个暗坑:如果那个被等到的从库恰好宕机,主库会自动降级成异步,RPO瞬间破防。所以很多生产环境里半同步的RPO其实是动态的,你敢信?你要真想用半同步,就得把“至少N”这个参数写死,还要确保从库数量足够,否则就是自欺欺人。

脑裂:容灾的头号死因

好,同步也做了,日志也落盘了,然后呢?你发现两个机房隔墙相望,中间的光缆被挖掘机一铲子切断。然后两边都发现对方“死了”,都觉得自己该上位,同时对外提供写服务。等到光缆修好,数据变成两本对不上的账——这就是脑裂。

解决脑裂的唯一硬道理:仲裁。不能光靠彼此的心跳,得有一个“第三方公证人”。在分布式共识算法里,这个公证人就是多数派(quorum)。一个节点要成为主,必须获得超过半数的投票。比如两个机房加一个独立仲裁节点,三票里至少拿两票。但仲裁放哪?这是门学问。

你说放机房A?那A宕机时一堆人就等。放云上?延迟和依赖又让人心慌。最好的做法是找个第三方机房放仲裁,或者用基础设施拉专线。但你别以为部署了quorum就万事大吉——Paxos/Raft里还藏着大量边界case,比如撕票时领导人反复横跳,或者旧主没死透又回光返照。这些工程细节,比论文里那几页伪代码恶心一百倍。

我遇到过一个真实事故:两个机房之间的专线断了,备机一直没收到心跳,等超时自动升主。可就在那一刻,主库的磁盘满了,正卡着没法响应。备机开始对外写,主库恢复后又接管,结果两边都以为自己是主,写坏了一片索引文件。后来排查发现,问题出在心跳线程和IO线程共用同一个连接池,网络拥塞时互相饿死。这难道不是设计上的一个嘲讽?

脑裂仲裁双机房quorum示意图
脑裂仲裁双机房quorum示意图

所以说,仲裁不光是算法,更是工程上对异常场景的穷尽。很多人以为搞个zookeeper就没事,但zk自己也可能分裂。成熟方案得用“fencing”机制——旧主被新主踢掉后,必须能够主动退出,比如借助SAN锁或自毁开关(kill自己)。这就是防止“死而复生”的绝招。

数据论证:同步到底多贵?

数据论证:同步到底多贵?
数据论证:同步到底多贵?

说来有趣,我在某支付系统做过一次压测。条件:主备机房相距50公里,光纤往返延迟1.2ms,数据库用MySQL 8.0,半同步复制,200个并发线程跑sysbench OLTP。结果:纯异步吞吐量38000 TPS,半同步直接掉到32800,降幅13.7%。但RPO从最坏情况下的9秒缩到0。这13.7%的代价,换的是“所有已提交的交易都不丢”。值吗?看企业命值多少钱。

但注意,同步复制的代价会随着延迟和连接数线性恶化。如果你把距离拉到200公里,往返延迟可能5ms,那同步复制几乎没法用。这时候就得靠串行化或group commit来压缩同步开销。真正的工程功夫就在这。

再给一组我压测中记录的网卡细节:千兆网卡下,binlog平均传输峰值跑到112 MB/s,同步复制时CPU开销多出约8%,因为要频繁计算ACK。当把复制模式改成“压缩+批量”后,吞吐量下降降到仅有5.2%,但实现复杂度上升得让你怀疑人生。

数据摆完后,你会明白“什么值得丢”才是容灾设计的出发点。0数据丢失不是免费的,它要求你付出至少10%以上的性能税。那些号称同步不损性能的,不是用了RDMA就是藏在你看不见的犄角旮旯里。

落地时的三个坑(我踩过的)

坑一:“假心跳”导致的反复切换。网络临时抖动可能让备机误判主库出局,触发切换,业务中断。解决:不要只依赖TCP心跳,要结合多个维度的探活,比如数据库内部状态、系统负载。最好采用lease机制,给主库一个租约,过期后才能选举。这能挡住短暂抖动。我一般把租约设成心跳间隔的三倍,比如心跳5秒,租约15秒,避免误伤。

坑二:异步复制的积压越滚越大。一旦链路带宽不足,binlog/redo堆积如山,主备数据偏移量飙升。等你想切换时,备机的数据可能落后好几个小时,那时你根本不敢切。解决:监控积压量,设置安全阈值,一旦库存超限自动降级读流量,或者切断同步,防止垃圾数据污染备份。更重要的是,你得有个定期演练的强制机制,逼着备机“追上来”。我见过有人用脚本每五分钟测一次偏移量,超过100MB就报警,但没人管,最后真出事就直接GG。

坑三:仲裁节点成了单点。把仲裁放在机房A?那机房A下线,仲裁也没了,这时候机房B不能自动接管。恶心的是,管理员以为还有仲裁,其实已经失效。解决:必须配置一个完全独立的仲裁节点,或者用三机房对称方案。如果你预算有限,至少在逻辑上模拟出“第三地”,不要和主备同命运。有一次我们用云上最小虚机做仲裁,结果云厂商通知说“物理机维护”,虚机漂移到别的物理机,连着断了三次,差点把系统毁了。

这三个坑,每一个都能让你深夜被电话叫醒。别问我怎么知道的。

容灾这事,永远是在用工程复杂度换确定性。你越了解底层机制,越明白没有银弹。只有把日志协议、仲裁策略、网络猜疑这些野兽关进笼子里,才敢说自己有点底子。别老盯着云厂商的SLA,那点数字代表不了你的数据命。

免责声明:市场有风险,选择需谨慎!此文仅供参考,不作买卖依据。如有侵权请联系删除。
文章名称:容灾的底牌:从日志复制到脑裂仲裁的工程博弈
文章链接:https://www.lfdjt.com/info_23_8411.html