持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
系统中的任何节点都可能宕机,对运维而言,能在系统不中断服务的情况下重启单个节点可太妙了。目标是即使个别节点失效,也能保持系统总体持续运行,并尽可能减小节点宕机的影响。
如何通过主从复制实现高可用呢?
从节点失效:追赶恢复
从节点的本地磁盘都保存了副本收到的数据变更日志。若从节点崩溃并重启或主、从节点之间网络中断,则比较容易恢复:从节点可从日志中知道,在发生故障之前处理的最后一个事务。因此,从节点可以连接到主节点,并请求在从节点断开连接时发生的所有数据变更。当应用完所有这些变化后,它就赶上了主节点,并可以像以前一样继续接收数据变更流。
主节点失效:故障切换
主节点故障则处理很棘手:
- 选择某个从节点提升为新的主节点
- 重新配置客户端,以将它们之后的写请求发给新的主节点
- 其他从节点开始接收来自新主节点的变更数据
该过程就是故障切换(failover)。
故障切换可手动进行,如:
- 通知管理员主节点宕机,采取必要步骤创建新的主节点
- 或自动进行
自动切换过程
-
确认主节点失效。有很多可能性:系统崩溃、停电或网络问题等。没有万无一失方法能确切检测到底啥问题,所以大多数系统都采用基于超时的机制:节点间频繁互发心跳存活消息,若发现某节点在一段时间内(如30s)无响应,就认为它挂了(因为计划内的维护目的而故意下线主节点的场景不算)
-
选一个新的主节点。可通过选举(剩余节点多数达成共识)或由之前选定的控制器节点(controller node)来指定新的主节点。最佳候选节点是拥有与原主节点的数据差异最小,以最小化数据丢失风险
-
重新配置系统以启用新的主节点
客户端现在需将写请求发给新主节点。若原主节点重归,可能仍认为自己是主节点,没意识到其他节点已达成共识迫使其下台。这时,系统要确保老领导认可新领导,并降级为一个从节点
故障切换的变数
若使用异步复制,则新主节点可能没收到老主节点宕机前的所有数据。选出新主节点后,若原主节点重新上线并加入集群,新主节点在此期间可能收到冲突的写请求,因为原主节点未意识到角色变化,还会尝试同步其他从节点,但其中的一个现在已接管成为新任主节点了。对此,常见解决方案:原主节点上未完成复制的写请求就此丢弃,但这可能会违背数据更新持久化的承诺。
若DB需和其他外部存储协作,则丢弃写入的内容是很危险的操作。如GitHub的一场事故,某个数据并非完全同步的MySQL从节点被提升为主节点,DB用自增计数器将主键分配给新
建的行,但因新主节点计数器落后于原主节点( 即二者并非完全同步),它重新使用已被原主节点分配出去的某些主键,而这些主键恰好已被外部Redis所使用,导致MySQL和Redis之间数据不一致,最后一些私有数据被错误地泄露给其他用户。
某些故障场景下可能会出现两个节点同时以为自己是主节点,即脑裂,很危险哦:两个主节点都可能接受写请求,且没有冲突解决机制,最好数据就可能丢失或损坏。某些系统对此采取安全措施:当检测到两个主节点同时存在时,会强制关闭其中一个节点ii,但设计粗糙的机制可能最后会导致两个节点都被关闭。
[ii] 这种机制称为 屏蔽(fencing) ,充满感情的术语是:爆彼之头(Shoot The Other Node In The Head, STONITH) 。
如何设置合适的超时来检测主节点失效呢?主节点失效后,超时时间越长,意味着总体恢复时间也越长。但若超时设置太短,又可能会频繁出现不必要的故障切换,如:
- 临时负载峰值可能导致节点响应时间超时
- 或网络故障可能导致数据包延迟
若系统已是高负载或网络拥塞,则不必要的故障切换可能让情况变得更糟。
这些问题其实都没简单解决方案。因此,即使软件支持自动故障切换,不少运维团队还是更愿意手动执行。
节点故障、不可靠的网络、副本一致性,持久性,可用性和延迟的各种权衡正是分布式系统核心问题。