故障转移原理:主备切换、读写分离、心跳检测、状态同步
这一章我们聊聊故障转移的四个核心机制。说白了,这四个东西就是高可用架构的四肢和大脑。我在一线的项目里跟它们打了十几年交道,每次出幺蛾子,基本都能在这四个点上找到根因。
先给个全景图,你大概有个印象:
一、主备切换:故障转移的“总开关”
主备切换,说白了就是「主挂了,备顶上」。听起来简单?我见过太多因为切换逻辑写得不够严谨导致的线上事故。
常见的切换模式有三种:
| 模式 | 切换时机 | 典型场景 | 风险点 |
|---|---|---|---|
| 主动切换 | 运维手动执行 | 计划内维护、版本升级 | 人为误操作 |
| 自动切换 | 心跳超时自动触发 | 进程宕机、网络分区 | 脑裂、误切 |
| 半自动切换 | 系统判定后需人工确认 | 数据库主从、消息队列 | 决策延迟 |
注意:自动切换一定要防「脑裂」——也就是两个节点都认为自己是主。我在一家金融公司遇到过,备机因为网络抖动认为自己失联了,直接抢主成功,结果两个主同时接受写入……要不是监控发现数据不一致报警,后果不敢想。
实现自动切换,你需要几个关键决策:
- 切换阈值:连续多少次心跳失败才触发?我习惯设3次,间隔1秒,总共3秒判定。
- 仲裁机制:多节点还是单节点判定?我最推荐奇数节点(如3个)做仲裁,避免网络分区时票数打平。
- 回切策略:原主恢复后自动切回去吗?建议手动回切,自动回切的坑太多,你想想看——万一原主只是短暂抖动,切回去又崩,来回震荡怎么办?
二、读写分离:把压力分散开
读写分离其实不是故障转移的“必需品”,但高可用系统基本都会用。为什么呢?因为主备切换最怕的就是切换后流量把备机打崩。读写分离提前把读流量分散到多个副本上,就算主挂了,备机也能扛住切换时的读请求。
常用的架构模式:
- 客户端直连:应用层配置读写数据源,简单直接。我曾经在初创公司用过,但数据源变更时需要重启或配置中心推送。
- 中间层代理:例如MyCat、ShardingSphere-Proxy、ProxySQL。你对应用透明,切换由代理层判断。我建议团队规模大、节点数多的场景用这个。
- DNS或VIP漂移:配合Keepalived做虚拟IP漂移。读写分离靠IP端口区分,但DNS缓存可能造成短暂不可用。
一个小技巧:写操作一定要保证到达主库,读操作可以容忍秒级延迟。但如果你强一致性要求,比如金融交易流水,读也得走主库——之前有个团队把「余额查询」也走读库,导致用户看到未到账的假象,被投诉了。
三、心跳检测:故障的“眼睛”
心跳检测就是定时发个「我还活着」的信号。但别小看它,检测参数设得不对,要么频繁误告警,要么故障拖到用户全挂。我总结过一套参数配置经验:
| 参数 | 值 | 说明 |
|---|---|---|
| 心跳间隔 | 1~3秒 | 太短增加开销,太长延迟发现 |
| 超时时间 | 3~5秒 | 考虑网络抖动,通常设为间隔×2~3 |
| 失败阈值 | 2~3次 | 连续N次无响应才判定故障 |
| 检测方式 | TCP Ping / HTTP / DB查询 | 选最贴近业务链路的检测 |
嗯,这里要注意:心跳检测不能只检测进程是否活着,还得检测服务是否可用。我踩过一个坑——Redis进程还在,但内存被打爆导致拒绝连接,心跳还能Ping通,结果切换没触发,一直到人工介入。
所以我现在都要求:心跳检测必须带上「业务探活」,比如访问一个关键接口或执行一条简单SQL。这样进程活着但业务僵死的情况也能被发现。
四、状态同步:切换后的数据不丢
主备切换最怕什么?数据丢失。状态同步就是解决这个问题的。把主库的最新状态实时或准实时同步到备库,切换时备库才能无缝接管。
常见的同步模式对比:
| 同步模式 | 数据一致性 | 性能影响 | 典型实现 |
|---|---|---|---|
| 同步复制 | 强一致 | 写入延迟增加 | MySQL半同步、Paxos/Raft |
| 异步复制 | 最终一致 | 几乎没有 | MySQL异步、Redis主从 |
| 半同步复制 | 至少一个备确认 | 折中 | MySQL semi-sync |
避坑指南:我曾在电商大促前调高了异步复制的同步延迟阈值,结果主库挂了,备库落后10秒数据。10秒的订单全部丢失,一个商品超卖了300多件。从那以后,但凡核心交易库,我都坚持用半同步或Raft协议。
状态同步还要关注「增量同步」和「全量同步」的区别。平时用增量,但如果备库落后太多或新加节点,就必须全量同步。全量同步期间要锁表吗?这就考验系统设计能力了。一些分布式存储(如TiKV)通过快照方式无锁全量同步,值得学习。
把它们串起来:一个典型的故障转移流程
你想想看,这四个机制在实际系统中是怎么配合的?我梳理一下:
- 心跳检测:每1秒发送,连续3次无响应。
- 故障判定:仲裁节点确认(至少2/3节点同意)。
- 状态同步检查:备库判断自己是否追上主库的最后状态。如果落后太多,可能拒绝切换或等待同步完成(取决于业务容忍度)。
- 主备切换:备库升级为主库,虚拟IP漂移,客户端连接刷新。
- 读写分离调整:新主库开始承担写入,读流量重新分配(可能有一部分读到旧主,但旧主已降为备,会返回只读错误)。
一个小建议:每次切换后,记得做数据校验。比如用pt-table-checksum检查MySQL主从一致性,或者用业务SQL比对关键记录。我在线上见过切换成功但数据产生歧义的案例,靠事后校验才修回来。
最后说一句:这四个机制不是独立的,你设计时要考虑它们之间的耦合度。心跳太灵敏会导致频繁切换,状态同步太慢会导致切换后数据丢失,读写分离策略不对会让切换成本大增。找个平衡点,多压测,多复盘——嗯,这是我用钱换来的教训。