Files
MIT6.824/lecture-06-raft1/6.6-ying-yong-ceng-jie-kou.md
2022-01-25 02:41:31 +00:00

4.9 KiB
Raw Permalink Blame History

6.6 应用层接口

这一部分简单介绍一下应用层和Raft层之间的接口。你或许已经通过实验了解了一些但是我们这里大概来看一下。假设我们的应用程序是一个key-value数据库下面一层是Raft层。

在Raft集群中每一个副本上这两层之间主要有两个接口。

第一个接口是key-value层用来转发客户端请求的接口。如果客户端发送一个请求给key-value层key-value层会将这个请求转发给Raft层并说请将这个请求存放在Log中的某处。

这个接口实际上是个函数调用称之为Start函数。这个函数只接收一个参数就是客户端请求。key-value层说我接到了这个请求请把它存在Log中并在committed之后告诉我。

另一个接口是随着时间的推移Raft层会通知key-value层你刚刚在Start函数中传给我的请求已经commit了。Raft层通知的不一定是最近一次Start函数传入的请求。例如在任何请求commit之前可能会再有超过100个请求通过Start函数传给Raft层。

这个向上的接口以go channel中的一条消息的形式存在。Raft层会发出这个消息key-value层要读取这个消息。所以这里有个叫做applyCh的channel通过它你可以发送ApplyMsg消息。

当然key-value层需要知道从applyCh中读取的消息对应之前调用的哪个Start函数所以Start函数的返回需要有足够的信息给key-value层这样才能完成对应。Start函数的返回值包括这个请求将会存放在Log中的位置index。这个请求不一定能commit成功但是如果commit成功的话会存放在这个Log位置。同时它还会返回当前的任期号term number和一些其它我们现在还不太关心的内容。

在ApplyMsg中将会包含请求command和对应的Log位置index

所有的副本都会收到这个ApplyMsg消息它们都知道自己应该执行这个请求弄清楚这个请求的具体含义并将它应用在本地的状态中。所有的副本节点还会拿到Log的位置信息index但是这个位置信息只在Leader有用因为Leader需要知道ApplyMsg中的请求究竟对应哪个客户端请求进而响应客户端请求

学生提问为什么不在Start函数返回的时候就响应客户端请求呢

Robert教授我们假设客户端发送了任意的请求我们假设这里是一个Put或者Get请求是什么其实不重要我们还是假设这里是个Get请求。客户端发送了一个Get请求并且等待响应。当Leader知道这个请求被Raftcommit之后会返回响应给客户端。所以这里会是一个Get响应。所以在Leader返回响应之前客户端看不到任何内容。

这意味着在实际的软件中客户端调用key-value的RPCkey-value层收到RPC之后会调用Start函数Start函数会立即返回但是这时key-value层不会返回消息给客户端因为它还没有执行客户端请求它也不知道这个请求是否会被Raftcommit。一个不能commit的场景是当key-value层调用了Start函数Start函数返回之后它就故障了所以它必然没有发送Apply Entry消息或者其他任何消息所以也不能执行commit。

所以实际上Start函数返回了随着时间的推移对应于这个客户端请求的ApplyMsg从applyCh channel中出现在了key-value层。只有在那个时候key-value层才会执行这个请求并返回响应给客户端。

有一件事情你们需要熟悉那就是首先对于Log来说有一件有意思的事情不同副本的Log或许不完全一样。有很多场合都会不一样至少不同副本节点的Log的末尾会短暂的不同。例如一个Leader开始发出一轮AppendEntries消息但是在完全发完之前就故障了。这意味着某些副本收到了这个AppendEntries并将这条新Log存在本地。而那些没有收到AppendEntries消息的副本自然也不会将这条新Log存入本地。所以这里很容易可以看出不同副本中Log有时会不一样。

不过对于Raft来说Raft会最终强制不同副本的Log保持一致。或许会有短暂的不一致但是长期来看所有副本的Log会被Leader修改直到Leader确认它们都是一致的。

接下来会有有关Raft的两个大的主题一个是Lab2的内容Leader Election是如何工作的另一个是Leader如何处理不同的副本日志的差异尤其在出现故障之后。