MIT6.824/lecture-06-raft1/6.5-ri-zhi-raft-log.md
2022-01-25 02:41:31 +00:00

6.3 KiB
Raw Permalink Blame History

6.5 日志Raft Log

你们应该关心的一个问题是为什么Raft系统这么关注LogLog究竟起了什么作用这个问题值得好好来回答一下。

Raft系统之所以对Log关注这么多的一个原因是Log是Leader用来对操作排序的一种手段。这对于复制状态机详见4.2而言至关重要对于这些复制状态机来说所有副本不仅要执行相同的操作还需要用相同的顺序执行这些操作。而Log与其他很多事物共同构成了Leader对接收到的客户端操作分配顺序的机制。比如说我有10个客户端同时向Leader发出请求Leader必须对这些请求确定一个顺序并确保所有其他的副本都遵从这个顺序。实际上Log是一些按照数字编号的槽位类似一个数组槽位的数字表示了Leader选择的顺序。

Log的另一个用途是在一个非Leader也就是Follower副本收到了操作但是还没有执行操作时。该副本需要将这个操作存放在某处直到收到了Leader发送的新的commit号才执行。所以对于Raft的Follower来说Log是用来存放临时操作的地方。Follower收到了这些临时的操作但是还不确定这些操作是否被commit了。我们将会看到这些操作可能会被丢弃。

Log的另一个用途是用在Leader节点Robert教授很喜欢这个特性。Leader需要在它的Log中记录操作因为这些操作可能需要重传给Follower。如果一些Follower由于网络原因或者其他原因短时间离线了或者丢了一些消息Leader需要能够向Follower重传丢失的Log消息。所以Leader也需要一个地方来存放客户端请求的拷贝。即使对那些已经commit的请求为了能够向丢失了相应操作的副本重传也需要存储在Leader的Log中。

所有节点都需要保存Log还有一个原因就是它可以帮助重启的服务器恢复状态。你可能的确需要一个故障了的服务器在修复后能重新加入到Raft集群要不然你就永远少了一个服务器。比如对于一个3节点的集群来说如果一个节点故障重启之后不能自动加入那么当前系统只剩2个节点那将不能再承受任何故障所以我们需要能够重新并入故障重启了的服务器。对于一个重启的服务器来说会使用存储在磁盘中的Log。每个Raft节点都需要将Log写入到它的磁盘中这样它故障重启之后Log还能保留。而这个Log会被Raft节点用来从头执行其中的操作进而重建故障前的状态并继续以这个状态运行。所以Log也会被用来持久化存储操作服务器可以依赖这些操作来恢复状态。

学生提问假设Leader每秒可以执行1000条操作Follower只能每秒执行100条操作并且这个状态一直持续下去会怎样

Robert教授这里有一点需要注意Follower在实际执行操作前会确认操作。所以它们会确认并将操作堆积在Log中。而Log又是无限的所以Follower或许可以每秒确认1000个操作。如果Follower一直这么做它会生成无限大的Log因为Follower的执行最终将无限落后于Log的堆积。 所以当Follower堆积了10亿不是具体的数字指很多很多Log未执行最终这里会耗尽内存。之后Follower调用内存分配器为Log申请新的内存时内存申请会失败。Raft并没有流控机制来处理这种情况。

所以我认为在一个实际的系统中你需要一个额外的消息这个额外的消息可以夹带在其他消息中也不必是实时的但是你或许需要一些通信来让Follower告诉LeaderFollower目前执行到了哪一步。这样Leader就能知道自己在操作执行上领先太多。所以是的我认为在一个生产环境中如果你想使用系统的极限性能你还是需要一条额外的消息来调节Leader的速度。

学生提问如果其中一个服务器故障了它的磁盘中会存有Log因为这是Raft论文中图2要求的所以服务器可以从磁盘中的Log恢复状态但是这个服务器不知道它当前在Log中的执行位置。同时当它第一次启动时它也不知道那些Log被commit了。

Robert教授所以对于第一个问题的答案是一个服务器故障重启之后它会立即读取Log但是接下来它不会根据Log做任何操作因为它不知道当前的Raft系统对Log提交到了哪一步或许有1000条未提交的Log。

学生补充问题如果Leader出现了故障会怎样

Robert教授如果Leader也关机也没有区别。让我们来假设Leader和Follower同时故障了那么根据Raft论文图2它们只有non-volatile状态也就是磁盘中存储的状态。这里的状态包括了Log和最近一次任期号Term Number。如果大家都出现了故障然后大家都重启了它们中没有一个在刚启动的时候就知道它们在故障前执行到了哪一步。所以这个时候会先进行Leader选举其中一个被选为Leader。如果你回顾一下Raft论文中的图2有关AppendEntries的描述这个Leader会在发送第一次心跳时弄清楚整个系统中目前执行到了哪一步。Leader会确认一个过半服务器认可的最近的Log执行点这就是整个系统的执行位置。另一种方式来看这个问题一旦你通过AppendEntries选择了一个Leader这个Leader会迫使其他所有副本的Log与自己保持一致。这时再配合Raft论文中介绍的一些其他内容由于Leader知道它迫使其他所有的副本都拥有与自己一样的Log那么它知道这些Log必然已经commit因为它们被过半的副本持有。这时按照Raft论文的图2中对AppendEntries的描述Leader会增加commit号。之后所有节点可以从头开始执行整个Log并从头构造自己的状态。但是这里的计算量或许会非常大。所以这是Raft论文的图2所描述的过程很明显这种从头开始执行的机制不是很好但是这是Raft协议的工作流程。下一课我们会看一种更有效的利用checkpoint的方式。

所以,这就是普通的,无故障操作的时序。