MIT6.824-Raft笔记3:Raft日志、应用层和raft之间的日志“传递“

1. 日志(Raft Log)

你们应该关心的一个问题是:为什么Raft系统这么关注Log,Log究竟起了什么作用?

  1. Log是Leader用来对操作排序的一种手段。这对于复制状态机(复制状态机基于:对于复制的服务 service 或者其它computer things,其内部操作都是确定的,除非有外部输入影响,详见4.2)而言至关重要,对于这些复制状态机来说,所有副本不仅要执行相同的操作,还需要用相同的顺序执行这些操作。Log与其他很多事物,共同构成了Leader对接收到的客户端操作分配顺序的机制。比如有10个客户端同时向Leader发出请求,Leader必须对这些请求确定一个顺序,并确保所有其他的副本都遵从这个顺序。实际上,Log是一些按照数字编号的槽位(类似一个数组),槽位的数字表示了Leader选择的顺序。
  2. 对于Raft的Follower来说,Log是用来存放临时操作的地方。在一个副本收到了操作,但是还没有执行操作时,该副本需要将这个操作存放在某处,直到收到了Leader发送的新的commit号才执行。Follower收到了这些临时的操作,但是还不确定这些操作是否被commit了,这些操作可能会被丢弃。
  3. Leader需要在它的Log中记录操作,因为这些操作可能需要重传给Follower。如果一些Follower由于网络原因或者其他原因短时间离线了或者丢了一些消息,Leader需要能够向Follower重传丢失的Log消息。Leader也需要一个地方来存放客户端请求的拷贝。即使对那些已经commit的请求,为了能够向丢失了相应操作的副本重传,也需要存储在Leader的Log中。
  4. 帮助重启的服务器恢复状态。你可能的确需要一个故障了的服务器在修复后,能重新加入到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)告诉Leader,Follower目前执行到了哪一步。这样Leader就能知道自己在操作执行上领先太多。是的,我认为在一个生产环境中,如果你想使用系统的极限性能,你还是需要一条额外的消息来调节Leader的速度。

个人理解:

  1. Leader会对齐Follower当前的Index,只需要从这里开始发送日志
  2. 每次并不是从上文的Index发到尾,二是有一个可配置的最大值控制
  3. Leader的Log会持久化到存储设备,未同步完成的日志无需存在内存中
  4. 快照机制,快速恢复

ETCD里面的leader会记录每一个副本的in flight消息的数目,同时也有设置的最大值控制这个值。

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

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

个人理解:

  1. 重选leader,因为一些限制条件(过半复制、过半投票),这个leader一定会有最全的日志
  2. 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知道这个请求被(Raft)commit之后,会返回响应给客户端。这里会是一个Get响应。(在Leader返回响应之前)客户端看不到任何内容。在实际的软件中,客户端调用key-value的RPC,key-value层收到RPC之后,会调用Start函数,Start函数会立即返回,但是这时,key-value层不会返回消息给客户端,因为它还没有执行客户端请求,它也不知道这个请求是否会被(Raft)commit。一个不能commit的场景是,当key-value层调用了Start函数,Start函数返回之后,它就故障了,它必然没有发送Apply Entry消息或者其他任何消息,也不能执行commit。实际上,Start函数返回了,随着时间的推移,对应于这个客户端请求的ApplyMsg从applyCh channel中出现在了key-value层。只有在那个时候,key-value层才会执行这个请求,并返回响应给客户端。

个人理解:

  1. Get请求可以同步一次日志,也可以不同步到日志中(这个困惑了好久,后面会具体说明)
    1. 如果允许短时间读到旧的数据,可以只从leader上读取数据(旧leader被网络隔离时,从旧leader上可能读到旧数据)
    2. 如果保障必须是最新的数据,可以对于每次读取都走一遍过半复制,也可以通过Read Index的方案实现,保障线性一致性。
  2. 对于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/)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/178631.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux下基于MPI的hello程序设计

Linux下基于MPI的hello程序设计 一、MPICH并行计算库安装实验环境部署创建SSH信任连接,实现免密钥互相连接node1安装MPICH 3.4配置NFS注意(一定要先看)环境测试 二、HELLO WORLD并行程序设计 一、MPICH并行计算库安装 在Linux环境下安装MPICH执行环境,配…

Linux(CentOS7.5):通过docker安装redis

一、准备配置文件 在宿主机,准备映射配置文件的目录下,运行如下: wget http://download.redis.io/redis-stable/redis.conf二、安装 docker run \ --restartalways \ --log-opt max-size100m \ --log-opt max-file2 \ -p 6380:6379 \ -v /opt…

[ 持续更新 ] Sprint Boot 常用注解汇总

Sprint Boot 常用注解 请求与响应 RequestMapping 可以给类或类的属性设置该注解,表示支持的所有 HTTP 请求方法,如 GET、POST、PATCH、DELETE 等 如果给类设置,表示该路径的前缀。给方法设置,表示让这个方法支持所有的请求方…

【浅尝C++】C++类的6大默认成员函数——构造、析构及拷贝构造函数

🎈归属专栏:浅尝C 🚗个人主页:Jammingpro 🐟记录一句:好想摆烂,又好想学习~~ 文章前言:本篇文章简要介绍C类的构造函数、析构函数及拷贝构造函数,介绍每个小点时&#xf…

【Linux专题】http(s)代理

【赠送】IT技术视频教程,白拿不谢!思科、华为、红帽、数据库、云计算等等_厦门微思网络的博客-CSDN博客文章浏览阅读444次。风和日丽,小微给你送福利~如果你是小微的老粉,这里有一份粉丝福利待领取...如果你是新粉关注到了小微&am…

【objectarx.net】table问题2:添加table后,保存时出错

添加table后,保存时出现以上对话框。 原因: tb.TableStyle db.Tablestyle; 对于这句代码,tb所在的数据库和db不是同一个。

java反射和注解3-仿照retrofit组装接口参数

本片文章将用反射和注解仿照retrofit只需要传入一个带有给定注解的接口,通过调用接口就能直接将传入的数据和注解进行结合,生成对应参数 1,自定义注解 对字段的修饰 Retention(RetentionPolicy.RUNTIME) Target(ElementType.PARAMETER) pu…

window配置完hosts电脑重启后莫名被还原

window配置完hosts电脑重启后莫名被还原 问题描述 之前用的好好的,这周开始出现问题。头天配置好的hosts,第二天开机后访问地址都是无法打开,后来发现是昨天hosts的配置都被还原了。原本电脑不关机,可是这几天电脑也总重启&…

Win7 SP1 x64 安装 Python 出错解决方法

1 双击安装 python-3.7.9.exe ,提示出错,log.file 显示需要 KB2533623,但在Microsoft Update Catalog 没有搜到,实验 KB4474419 也可以。 2 Microsoft Update Catalog 搜索 KB4474419 ,选择 x64 位,下载&am…

Cache学习(4):Cache分配策略Cache更新策略Cache逐出策略

Cache的数据流 常用名词 Allocation 分配Eviction 驱逐分配策略和更新策略分别为当产生Cache miss和Cache hit的时候数据流的具体行为 1 Cache分配策略(Cache Allocation Policy) Cache的分配策略是指不同情况下为数据分配Cache Line的不同行为。Cac…

基本数据结构二叉树(3)

目录 4.二叉树链式结构的操作 4.1 前置说明 4.2二叉树的遍历 4.2.1 前序、中序以及后序遍历 4.3 节点个数以及高度等 4.二叉树链式结构的操作 4.1 前置说明 由于博主对二叉树的结果掌握还不够深入,因此在讲解相关操作前将手动创建一颗简单的二叉树&#xff0c…

金字塔原理 读书笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言第1篇 表达的逻辑第1章 为什么要用金字塔结构归类分组,将思想组织成金字塔自上而下表达,结论先行自下而上思考,总结概括 第2…

Unity 接入TapADN播放广告时闪退 LZ4JavaSafeCompressor

通过跟踪安卓日志,发现报如下错误 Didnt find class "com.tapadn.lz4.LZ4JavaSafeCompressor" 解决方案: 去掉Minify这边的勾选,再打包即可。

trino push down fliter

依据trino 432 版本而写 1. 调用链 2. 先是元数据,然后做扫描数据层的filter pushFilterIntoTableScan

数据导入与预处理-第7章-数据清理工具OpenRefine

文章目录 数据清理工具OpenRefineOpenRefine简介下载与安装配置创建项目操作列收起列移动列和重排列移除该列与移除列重新定义列标题撤销与重做导出数据 进阶操作数据排序数据归类重复检测数据填充文本过滤数据转换 总结 数据清理工具OpenRefine OpenRefine简介 OpenRefine是…

【论文阅读】【基于隐蔽带宽的汽车控制网络鲁棒认证】中的一些顶会论文摘要

读摘要,了解面貌 文章目录 [12][51][58][35][xx] 原文:https://webofscience.clarivate.cn/wos/alldb/full-record/WOS:000387820900034 Large numbers of smart connected devices, also named as the Internet of Things (IoT), are permeating our en…

深度学习之基于百度飞桨PaddleOCR图像字符检测识别系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介主要特点使用步骤 二、功能三、系统四. 总结 一项目简介 # Introduction to PaddleOCR Image Character Detection and Recognition System Based on Baidu…

享元模式-C++实现

享元模式(FlyWeight Pattern)是一种结构型设计模式,旨在减少对象创建的数量,节省内存和提高性能。 在某些情况下,一个项目里可能需要创建大量相似的对象,这样对象的一部分是共享的(相同的&…

Java基础-中级-高级面试题汇(一)

第一部分: Java基础面试题汇总 1.面向对象和面向过程的区别? 面向对象和面向过程是两种不同的编程思想。面向对象是一种以对象为中心的编程思想,将数据和处理数据的方法封装在一起,形成一个类。程序通过创建对象来调用类中的方法…

静态住宅IP代理实际应用:它的强大用途你知道吗?

静态住宅IP代理与动态IP代理相比,提供了更稳定的网络身份,使得企业在进行数据采集、区域定位营销和市场研究时更为高效。同时,它也是提高在线隐私保护和避免封禁的有效工具。 通过详细分析,你将能全面了解静态住宅IP代理的应用&a…