TT Lab
开始
学习 学习路径 课程

当时有两个领导者

隔离、两边都写、再恢复

在 TT Lab 中继续学习

目标

把三个节点中的一个从其余节点上隔离开再恢复,亲自观察同一时刻出现两个领导者的状态, 以及那时写入各侧的值的命运。

为什么重要

分布式系统的事故,通常不是“故障”,而是来自不完整的信息。被隔离的领导者 不知道自己被隔离了。它所知道的只有“最近没有响应”,而 Raft 的领导者没有 自行退位的机制。所以它会继续像领导者一样行事,接收客户端的值, 并返回 ok。同一时刻,在另一侧,过半数节点聚在一起,选出任期更高的新领导者。

这时领导者确实是两个。Raft 阻止的不是出现两个领导者,而是同一任期内出现两个 领导者,而少数派一侧的领导者得不到过半数的确认,所以什么都确定不了。 恢复的那一刻,它的未确定条目会悄无声息地消失。这个实验留下的问题只有一个—— 在这期间收到 ok 的客户端会怎么样?

步骤

  1. 把三个文件放到 /root/raft/,启动三个节点,并写入 /root/raft/leader.json。
  2. 在 /root/raft/net.py 中编写 should_deliver(src, dst, cut)。
  3. 写入值 alpha 并使其确定,写入 /root/raft/before.json。
  4. 让领导者与其余两个节点互相断开,写入 /root/raft/isolated.json。
  5. 等待新领导者被选出,写入 /root/raft/two-leaders.json。
  6. 向旧领导者写入 ghost,写入 /root/raft/minority.json。
  7. 向新领导者写入 real,写入 /root/raft/majority.json。
  8. 解除断开,把结果写入 /root/raft/verdict.json。

参考

建立三个节点

把 /opt/lab/raft/ 中的 node.py、rules-for-partition.py、log-for-partition.py 分别放到 /root/raft/ 的 node.py、raftrules.py、raftlog.py,启动三个节点,并把选出的领导者写入 /root/raft/leader.json。

前两个实验中写过的规则,会以完成版的形式提供给你。这个实验中你要写的代码只有一个分区开关,其余的都是动手实验的时间。在 leader.json 中写 leader 和 term。

不断线,而是拒绝消息

在 /root/raft/net.py 中编写 should_deliver(src, dst, cut)。不收发 cut 中所列对方的消息。

这个 Pod 没有设置防火墙的权限。所以我们不去“断线”,而是让节点拒绝特定对方的请求——对对方来说,这与没有响应没有区别,所以站在共识算法的立场上,它与真正的分区是一样的。dst 是我自己,cut 是我已经断开的对方的列表。用 curl -s -XPOST -d '{"peers":[2,3]}' 127.0.0.1:5001/cut 设置,并通过 /status 的 cut 来确认。

在分区之前先确定一个值

向领导者写入值 alpha,确认三个节点的 commit 都变成 1 之后,把 value、commit、committed_on 写入 /root/raft/before.json。

要分清之后哪些被丢弃、哪些留下,就必须有一个在分区之前已经确定的值。committed_on 是提交了该值的节点数。写入的命令是 curl -s -XPOST -d '{"value":"alpha"}' 127.0.0.1:<리더포트>/client(占位符为领导者端口)。

把领导者隔离出去

让领导者与其余两个节点互相断开,并把刚断开之后旧领导者的状态,以 old_leader、state_after_cut、cut 写入 /root/raft/isolated.json。

两侧都要设置,才是真正的分区——只设置一侧,一个方向仍然是通的。看看被断开的领导者会不会自行退位。Raft 的领导者没有降级自己的机制。在看到更大的任期之前,它会一直相信自己是领导者。

同一时刻有两个领导者

等待多数派一侧选出新领导者,然后把两个领导者的编号和任期,以 old_leader、old_term、new_leader、new_term 写入 /root/raft/two-leaders.json。

这就是本课程的标题。把三个节点的 /status 并排放在一起看,会有两个节点同时回答自己是 leader。但是它们的任期不同。 Raft 阻止的不是“出现两个领导者的状态”,而是“同一任期内出现两个领导者的状态”,这个区分正是这个算法的安全性所立足的地方。

写入少数派一侧的值

向被断开的旧领导者写入值 ghost,并把 accepted、committed、log_len、commit 写入 /root/raft/minority.json。

被断开的领导者也会接收这个值。它会写进自己的日志,并向客户端返回 ok。但是由于没有来自过半数的确认,commit 停留在 1。收到的和已确定的之间的距离,在这里看得见了——如果不了解这个距离的客户端把 ok 当作成功,就会出事故。

写入多数派一侧的值

向新领导者写入值 real,并把 index、committed、commit、same_index_as_ghost 写入 /root/raft/majority.json。

关键在于这两个值拿到了同一个位置编号。ghost 和 real 在争夺日志的第 2 个位置,而一个位置上只能留下一个。留下哪一个已经确定——得到过半数确认的那一个。

恢复——什么会被丢弃

解除三个节点的全部断开,确认旧领导者的日志被整理之后,把 survived、discarded、old_leader_state_after、ghost_was_committed、acknowledged_to_client 写入 /root/raft/verdict.json。

解除断开用 curl -s -XPOST -d '{"peers":[]}' 127.0.0.1:<포트>/cut(占位符为端口)。旧领导者在看到更大任期的心跳的那一刻就会退位,之后它日志的第 2 个位置会被新领导者的内容覆盖。最后两项就是这个实验的教训——那个值从未被提交过,但客户端却收到了 ok。