节点随时会挂,这不是概率问题,是时间问题。真正难的地方不在”转移”这个动作本身,而在于转移过程中用户无感知、数据不冲突、系统不失控。下面按工程落地的顺序拆开讲。
先搞清楚转移要解决的三个问题
自动转移的目标很朴素:不靠人肉介入,把故障节点手上的活交给健康节点。
但落地时会撞上三件事——怎么确认对方真死了、怎么防止两边同时自立为王、怎么处理没干完的活。这三件事没想清楚,配置写得再漂亮也是纸面高可用。
心跳检测:阈值调不好,误切比不切更糟
心跳是最常见的故障发现手段。节点周期性往协调服务或对等节点发心跳包,连续几次收不到就判定对方下线。
坑在于网络抖动和瞬时丢包会造成误判。生产环境通常要叠加超时阈值和重试次数,比如连续三次无响应才触发动作。
我见过心跳间隔设得过短的系统,网络一抖就切主,切完又切回来,可用性反而被自己拉低。这个值没有万能解,必须结合网络质量和业务容忍度来定。
判定标准可以这样设:连续 N 次心跳丢失触发疑似故障,进入观察窗口;观察窗口内仍未恢复才执行转移。观察窗口长度建议大于一次完整 GC 或网络重传周期。
无状态和有状态,转移路径完全不同
Web 这类无状态服务好办,直接在负载均衡层摘除故障节点,流量打到剩余节点即可。
数据库、缓存、分布式存储这类有状态服务就麻烦得多,必须靠一致性算法选新主,Paxos 和 Raft 是两条主流路线。
以 Raft 为例:Follower 在 election timeout 内没收到 Leader 心跳,就自增 term 发起选举,拿到多数票即成为新 Leader。
这里有个容易被忽略的点:选举期间未同步的日志可能丢失,所以生产上一般要配合 WAL 预写日志和副本同步,把最终一致性兜住。

脑裂:转移流程里最难缠的对手
网络分区时,两边都以为对方死了,各自选出主节点,数据就开始打架。这是自动转移的头号敌人。
标准解法是多数派原则:提案只有拿到超过半数节点支持才生效。这样即使分区,最多只有一个分区能凑出多数派,另一个自然瘫痪,不会产生双主。ZooKeeper 和 etcd 都是这个思路。
有些场景会额外部署仲裁节点,分区时投出决定性一票。但仲裁机自己也是单点,所以更稳的做法是用奇数节点天然形成多数。
三节点集群能容忍一个节点挂,五节点能容忍两个,这是多数派原则的直接推论。
数据复制:同步、异步、半同步怎么选
主从复制、多副本复制都能在副本异常时让其他副本顶上。
同步复制延迟高但不丢数据,异步复制性能好但故障时可能丢一段。半同步是折中路线,MySQL 组复制和 MongoDB 副本集都支持这类取舍。
有个做金融系统的朋友强制上同步复制加自动转移,写入延迟明显上升,但对他们来说数据不丢比快几毫秒重要得多。选哪种,取决于业务能不能接受丢数据。
上层配合:熔断、重试和服务网格
底层机制之外,应用层也要配合。下游挂了,上游必须快速熔断,别让请求堆积成雪崩。
等一段时间再试探重连,通了就恢复,不通继续熔断。Hystrix 和 Resilience4j 都是这个思路的工具。
服务网格让这件事更透明。Istio 这类方案在 Sidecar 层做探活和流量切换,应用代码几乎不用动。代价是多了一层代理,排查问题时链路更长。

演练才是真正的验收
配置写得再完美,不演练都是纸上谈兵。故意杀掉一个节点,看系统能不能在约定时间内恢复。
很多团队一演练就露馅:健康检查超时太长、选举耗时过久、连接池没清理干净导致恢复后继续报错。
验证方法很直接:注入故障后记录恢复时间,对比 SLA 要求;同时检查恢复期间是否有请求失败、数据是否一致。
判定标准建议写成硬指标,比如恢复时间小于 X 秒、数据零丢失、错误率不超过阈值。达不到就回去调参数。

一个容易踩的部署坑
Raft 集群初始化时,如果节点数配成偶数,多数派计算会出现尴尬情况。比如四节点集群,多数派是 3,容忍度反而和三节点一样,白白多花一台机器。
另外时钟漂移会影响 election timeout 的判断。容器环境里如果没配好时间同步,节点间时钟差过大,可能导致选举频繁触发。部署时务必确认 NTP 或 chrony 正常工作。
写在最后
故障转移不是开箱即用的功能,而是一整套从检测、决策、执行到恢复的流程。
关键不在于机器挂了能不能转,而在于转的过程中用户无感、数据不冲突、系统不失控。高可用是构建出来的,不是加出来的。
不管是 Raft 还是自研租约机制,目标都是让故障变成代码里一个普通异常分支,而不是让整个系统蓝屏。