分布式:数据复制
可以把“数据复制”理解为:
同一份数据保存在多台服务器上,一台坏了,还能从其他服务器读取或恢复。
但仅仅复制多份还不够,系统还要解决:写入顺序、何时算成功、节点故障后由谁接管,以及不同副本如何重新同步。
一、最简单的多副本结构
假设有三个节点:
节点 A:x = 0 节点 B:x = 0 节点 C:x = 0客户端希望执行:
SET x = 100在 Raft 这类系统中,节点分为:
A:Leader B:Follower C:Follower所有写请求先交给 Leader:
客户端 | | SET x = 100 v Leader A |----------------> Follower B | +----------------> Follower CLeader 负责规定操作顺序,Follower 按照这个顺序复制和执行。这样客户端虽然面对多台服务器,但逻辑上像在操作一台可靠的服务器。
二、一次写入是如何复制的
第一步:写入 Leader 日志
客户端发送:
SET x = 100Leader 先把它记录到日志中:
A 的日志: Index Term Command 1 1 SET x = 10 2 2 SET x = 100此时日志 2 只是“Leader 收到了”,还不能立即认为操作成功。
第二步:发送给其他节点
Leader 通过AppendEntries把日志复制给 B、C:
A:[1, 2] | +---- 日志 2 ----> B:[1, 2] | +---- 日志 2 ----> C:[1, 2]Follower 会先把日志持久化,再返回确认。
第三步:等待多数节点确认
假设 C 暂时断网:
A:保存成功 B:保存成功 C:没有响应三节点集群的多数是两个,因此 A 和 B 已经构成多数:
3 个节点,多数 = 2 5 个节点,多数 = 3 7 个节点,多数 = 4Leader 此时可以把日志标记为Committed,然后应用到状态机,并通知客户端写入成功。共识系统只要多数节点仍可通信,通常就能继续推进;五节点集群可以容忍两个节点故障。
A:x = 100,已提交 B:x = 100,已提交 C:暂时还是旧数据三、为什么多数确认很重要
假设一条数据只保存在 Leader 上就返回成功:
A:有日志 2,并向客户端返回成功 B:没有日志 2 C:没有日志 2如果 A 马上损坏,日志 2 就彻底丢失了:
A:故障 B:[1] C:[1]这意味着客户端明明收到“成功”,数据却消失了。
如果要求多数节点保存:
A:[1, 2] B:[1, 2] C:[1]即使 A 故障,B 仍然拥有日志 2,可以参与选举并成为新 Leader。
所以多数确认的意义是:
一条已经宣布成功的数据,不能只存在于一台可能损坏的服务器上。
四、复制如何提高可用性
没有副本时:
客户端 -> 节点 A A 故障 -> 整个服务不可用有三个副本时:
客户端 -> Leader A | +--> B +--> C如果 Follower C 故障:
A:正常 B:正常 C:故障A 和 B 仍然构成多数,系统可以继续处理请求。
如果 Leader A 故障:
A:故障 B:正常 C:正常B、C 会重新选举。其中拥有最新合格日志的节点成为新 Leader:
B:新 Leader C:Follower客户端之后把请求发送给 B,服务得以恢复。Raft 的选举限制要求候选者的日志至少与投票节点一样新,防止缺少已提交记录的节点当选。
五、复制如何提高容错性
容错性表示系统的一部分发生故障时,整体仍然能够正确运行。
从节点故障
A:Leader,正常 B:Follower,正常 C:Follower,故障系统还有多数节点,继续工作。
C 恢复后,会向 Leader 补齐缺少的日志:
恢复前: A:[1, 2, 3, 4] B:[1, 2, 3, 4] C:[1, 2] 同步后: C:[1, 2, 3, 4]Leader 故障
剩余节点重新选举,新 Leader 接管请求。因为选举多数与提交多数必然存在重叠节点,再加上日志新旧检查,已经提交的日志会被后续 Leader 保留。
数据盘故障
只要其他副本仍然保存数据,故障节点修复后就可以从正常节点重新同步。
六、网络分区时会发生什么
假设五个节点被分成两组:
多数一侧:A、B、C 网络中断 少数一侧:D、E多数一侧有三个节点,可以选举 Leader、复制日志并继续提交。
少数一侧只有两个节点:
D + E < 多数 3因此它们不能提交写入。即使旧 Leader 位于少数一侧,也不能在没有多数确认的情况下向客户端返回成功。
这会牺牲少数一侧的可用性,但能防止两边同时确认冲突数据:
多数一侧:x = 100 少数一侧:x = 200网络恢复后,少数一侧会接受新 Leader,并删除或覆盖未提交的冲突日志。因此 Raft 的取舍总体属于 CAP 中的CP:发生网络分区时,优先保证一致性。
七、为什么不等待所有节点
假设系统规定必须三台全部写入成功:
A 成功 + B 成功 + C 成功 -> 返回成功只要 C 故障,所有写请求都会失败。数据一致性很好,但可用性很差。
如果只等待一台:
A 成功 -> 立即返回速度快、暂时更可用,但 A 故障时可能丢失刚写的数据。
多数派是一种折中:
三台中写入两台 -> 成功 五台中写入三台 -> 成功它允许少量节点故障,同时又能保护已提交的数据。
八、强一致和最终一致是两条不同路线
Raft:强一致路线
写入 Leader -> 复制到多数 -> 标记提交 -> 返回成功如果无法联系多数节点,就停止提交,避免返回错误结果。
Dynamo 类系统:高可用路线
另一类系统允许多个可用节点继续接收写入:
节点 A 接收:x = 100 节点 B 接收:x = 200网络恢复后再通过版本号、时间戳或业务规则解决冲突。这种复制方式能够获得更高的分区可用性,但可能只能提供最终一致性。Amazon 的 Dynamo 设计通过多副本、类 quorum 技术和版本冲突处理,选择在部分故障场景中牺牲强一致性来提高可用性。
九、最重要的区分
复制成功: 数据已经保存到某个副本 提交成功: 数据已经满足系统的确认规则,可以对外宣布成功 执行成功: 已提交日志已经应用到数据库或状态机在 Raft 中,完整过程可以记成:
客户端写入 ↓ Leader 记录日志 ↓ 复制到 Followers ↓ 多数节点确认 ↓ 日志提交 ↓ 各节点按顺序执行 ↓ Leader 返回成功因此,真正保证可用性和容错性的不是“复制”这一个动作,而是:
多副本保存 + 多数确认 + Leader 选举 + 日志补齐 + 冲突日志修复。
另外,多副本不等于备份。误删除或错误命令也可能被迅速复制到所有节点,所以实际系统通常还需要独立备份。
