分布式:数据复制

📅 2026/7/22 6:19:31 👁️ 阅读次数 📝 编程学习
分布式:数据复制

可以把“数据复制”理解为:

同一份数据保存在多台服务器上,一台坏了,还能从其他服务器读取或恢复。

但仅仅复制多份还不够,系统还要解决:写入顺序、何时算成功、节点故障后由谁接管,以及不同副本如何重新同步。

一、最简单的多副本结构

假设有三个节点:

节点 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 C

Leader 负责规定操作顺序,Follower 按照这个顺序复制和执行。这样客户端虽然面对多台服务器,但逻辑上像在操作一台可靠的服务器。

二、一次写入是如何复制的

第一步:写入 Leader 日志

客户端发送:

SET x = 100

Leader 先把它记录到日志中:

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 个节点,多数 = 4

Leader 此时可以把日志标记为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 选举 + 日志补齐 + 冲突日志修复。

另外,多副本不等于备份。误删除或错误命令也可能被迅速复制到所有节点,所以实际系统通常还需要独立备份。