房屋自建设计哪个网站好,网站建设数据技术,wordpress stop,wordpress 插件开发教程1. 日志#xff08;Raft Log#xff09;
你们应该关心的一个问题是#xff1a;为什么Raft系统这么关注Log#xff0c;Log究竟起了什么作用#xff1f;
Log是Leader用来对操作排序的一种手段。这对于复制状态机#xff08;复制状态机基于#xff1a;对于复制的服务 ser…1. 日志Raft Log
你们应该关心的一个问题是为什么Raft系统这么关注LogLog究竟起了什么作用
Log是Leader用来对操作排序的一种手段。这对于复制状态机复制状态机基于对于复制的服务 service 或者其它computer things其内部操作都是确定的除非有外部输入影响详见4.2而言至关重要对于这些复制状态机来说所有副本不仅要执行相同的操作还需要用相同的顺序执行这些操作。Log与其他很多事物共同构成了Leader对接收到的客户端操作分配顺序的机制。比如有10个客户端同时向Leader发出请求Leader必须对这些请求确定一个顺序并确保所有其他的副本都遵从这个顺序。实际上Log是一些按照数字编号的槽位类似一个数组槽位的数字表示了Leader选择的顺序。对于Raft的Follower来说Log是用来存放临时操作的地方。在一个副本收到了操作但是还没有执行操作时该副本需要将这个操作存放在某处直到收到了Leader发送的新的commit号才执行。Follower收到了这些临时的操作但是还不确定这些操作是否被commit了这些操作可能会被丢弃。Leader需要在它的Log中记录操作因为这些操作可能需要重传给Follower。如果一些Follower由于网络原因或者其他原因短时间离线了或者丢了一些消息Leader需要能够向Follower重传丢失的Log消息。Leader也需要一个地方来存放客户端请求的拷贝。即使对那些已经commit的请求为了能够向丢失了相应操作的副本重传也需要存储在Leader的Log中。帮助重启的服务器恢复状态。你可能的确需要一个故障了的服务器在修复后能重新加入到Raft集群要不然你就永远少了一个服务器。比如对于一个3节点的集群来说如果一个节点故障重启之后不能自动加入那么当前系统只剩2个节点那将不能再承受任何故障我们需要能够重新并入故障重启了的服务器。对于一个重启的服务器来说会使用存储在磁盘中的Log。每个Raft节点都需要将Log写入到它的磁盘中这样它故障重启之后Log还能保留。而这个Log会被Raft节点用来从头执行其中的操作进而重建故障前的状态并继续以这个状态运行。Log也会被用来持久化存储操作服务器可以依赖这些操作来恢复状态。 2.中比如5节点只在leader和另一个follower上面成功记录那么这个follower记录的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的速度。 个人理解 Leader会对齐Follower当前的Index只需要从这里开始发送日志每次并不是从上文的Index发到尾二是有一个可配置的最大值控制Leader的Log会持久化到存储设备未同步完成的日志无需存在内存中快照机制快速恢复 ETCD里面的leader会记录每一个副本的in flight消息的数目同时也有设置的最大值控制这个值。 学生提问如果其中一个服务器故障了它的磁盘中会存有Log因为这是Raft论文中图2要求的服务器可以从磁盘中的Log恢复状态但是这个服务器不知道它当前在Log中的执行位置。同时当它第一次启动时它也不知道那些Log被commit了。
Robert教授对于第一个问题的答案是一个服务器故障重启之后它会立即读取Log但是接下来它不会根据Log做任何操作因为它不知道当前的Raft系统对Log提交到了哪一步或许有1000条未提交的Log。 个人理解 重选leader因为一些限制条件过半复制、过半投票这个leader一定会有最全的日志leader当选之后会先确定commitID 学生补充问题如果Leader出现了故障会怎样
Robert教授我们来假设Leader和Follower同时故障了那么根据Raft论文图2它们只有non-volatile状态。这里的状态包括了Log和最近一次任期号Term ID。如果大家都出现了故障然后大家都重启了它们中没有一个在刚启动的时候就知道它们在故障前执行到了哪一步。这个时候会先进行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协议的工作流程。
2. 应用层和raft库之间的接口
这一部分简单介绍一下应用层和Raft层之间的接口。假设我们的应用程序是一个key-value数据库下面一层是Raft层。在Raft集群中每一个副本上这两层之间主要有两个接口。
key-value层用来转发客户端请求的接口。如果客户端发送一个请求给key-value层key-value层会将这个请求转发给Raft层并说请将这个请求存放在Log中的某处。这个接口实际上是个函数调用只接收一个参数就是客户端请求。key-value层说我接到了这个请求请把它存在Log中并在committed之后告诉我。Raft层通知key-value层请求已经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 ID和一些其它我们现在还不太关心的内容。在ApplyMsg中将会包含请求command和对应的Log位置index。所有的副本都会收到这个ApplyMsg消息它们都知道自己应该执行这个请求弄清楚这个请求的具体含义并将它应用在本地的状态中。所有的副本节点还会拿到Log的位置信息index但是这个位置信息只在Leader有用因为Leader需要知道ApplyMsg中的请求究竟对应哪个客户端请求进而响应客户端请求。 个人理解 这里这种描述有点指定了具体的代码实现感觉应用层同步等待结果raft层异步处理这种方式应用层比较简单。第二个接口简单来说就是leader接收到应用层的请求之后会执行raft log提交发送给其它follower当达到quorum的时候就应用并且返回给上层结果。这里面描述的方案应用层还得去知道返回的对应哪一个操作感觉代码实现起来比较复杂不如把底层实现封装起来可以用feature的模式去等待。[go用channel实现feature对性能的影响]对raft层来说是异步处理的但是从应用层的视角里应用层是在同步等待raft层的结果。 学生提问为什么不在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层才会执行这个请求并返回响应给客户端。 个人理解 Get请求可以同步一次日志也可以不同步到日志中这个困惑了好久后面会具体说明 如果允许短时间读到旧的数据可以只从leader上读取数据旧leader被网络隔离时从旧leader上可能读到旧数据如果保障必须是最新的数据可以对于每次读取都走一遍过半复制也可以通过Read Index的方案实现保障线性一致性。对于leader来说需要确保日志同步完成才能响应。 感觉这里还是教授对raft实际代码实现的理解在应用层异步感觉不如在raft层异步应用层用起来舒服。 对于Log来说有一件有意思的事情不同副本的Log或许不完全一样。有很多场合都会不一样至少不同副本节点的Log的末尾会短暂的不同。例如一个Leader开始发出一轮AppendEntries消息但是在完全发完之前就故障了。这意味着某些副本收到了这个AppendEntries并将这条新Log存在本地。而那些没有收到AppendEntries消息的副本自然也不会将这条新Log存入本地。这里很容易可以看出不同副本中Log有时会不一样。Raft会最终强制不同副本的Log保持一致。或许会有短暂的不一致但是长期来看所有副本的Log会被Leader修改直到Leader确认它们都是一致的。 参考文献 [https://pdos.csail.mit.edu/6.824/schedule.html](https://pdos.csail.mit.edu/6.824/schedule.html) [https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/](https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/)