6.824 lab2 raft实现

news2024/11/16 11:57:42

目录

1 难点及完成思路:

1.1 难点分析:

1.2 完成思路:

1.3 实验心得:

1.4 报告结构:

2 Leader选举模块

2.1 思路概括(代码角度)

2.2 注意问题:

2.3 代码细节(对应第2.1节框架各个部分)

2.4 运行结果

3 日志复制模块

3.1 思路概括(代码角度):

3.2 注意点

3.3 细节代码

3.4 运行结果

4 持久化模块

5 日志压缩/快照模块


6.824 实现报告

1 难点及完成思路:

1.1 难点分析:

本次课程作业为复现论文中Raft算法,推荐语言为go,参考框架也已经给出。Raft算法本身思想感觉并没有难以理解的地方,但因为之前几乎从未接触过这类分布式/并发控制/多线程的代码,所以,第一个难点就是不会写多线程,不了解多线程代码的运行逻辑;另一个难点就是尽管参考框架中有一定的注释,但看完以后感觉还是不太能理解代码各个模块之间的关系,尤其是不能理解自己需要在参考框架定义的那些函数里进行哪些操作,以及这些功能是怎么被调用的也不太明白。

1.2 完成思路:

因此,基于上述分析,作业的完成思路分为如下两步,第一步通过开源代码弄清各个模块间的逻辑关系,第二步尝试自己复现代码。

1.3 实验心得:

因为第二步复现代码前已经知道了开源代码的一些实现思路,所以相比完全从零开始复现,这样做必然从一定程度上间接地少踩了不少坑,不过实现过程中还是遇到了许多问题,写了无数个中间输出来调bug。例如该实验中,有许多地方涉及了多线程/协程的竞态问题,因此就需要合理的使用锁,当然目前来看本次作业中的竞态问题是比较好解决的问题,通过-race参数的报错在相应地方加锁即可。目前感觉代码逻辑的严谨性才是最大的难点,例如发投票/发送心跳/发日志的过程,无论对于信息的接受方还是发送方,都需要通过对接受/反馈得到的信息来判断自己目前所应处的三种节点状态,这个问题在论文的Figure 2中是有提到的,另外自己参考的代码中也有对应实现,但自己复现时就没考虑周全,以至于最后对着参考代码一行一行的找问题,自己写的代码和参考代码还有很多的地方思路不太一样,就导致调bug变的异常困难。

1.4 报告结构:

接下来报告中将在2、3、4、5节中详细介绍选举任务、日志复制任务、持久化任务以及日志压缩任务的实现过程。

2 Leader选举模块

整个选举算法的基本思想是:

初始所有节点处于Follower状态,并随机一个固定区间的[150, 300]或者[500, 600]的选举超时时间,如果在该时间内节点没有收到心跳信息,就变为Candicate状态并发起选举,如果网络中一半以上节点同意它的选举申请,则该节点转为Leader状态,并周期性的向其他节点发送心跳包来保持自己的Leader地位

2.1 思路概括(代码角度)

首先,从各个函数之间的交互关系来看,Make函数可以看作整个程序的入口,在test.go文件或者config.go中,会通过调用Make函数来生成网络中的N个节点,同时在Make函数中,完成对节点的初始化以及启动相应的选举Goroutine/协程,代码步骤概括如下(具体细节见2.3):

①定义所需数据结构:Raft(表示一个节点)、RequestVoteArgs(Candidate发出的投票申请信息)、RequestVoteReply(投票申请的回应信息)、AppendEntriesArgs(该模块中Leader用一个空的该结构发送心跳,后面也会用该结构发日志)、AppendEntriesReply(心跳/日志回复信息)

②Make函数对节点进行初始化,并开启选举协程GoElection()和心跳协程GoHeartbeat()。

③实现GoElection()函数和GoHeartbeat()函数,前者功能为向其他节点发送投票申请,并根据投票反馈信息决定自己成为Leader状态还是变回Follower状态。后者Leader节点才会执行,用于向其他节点发送心跳信息。

④实现AppendEntries(…)、RequestVote(…),前者用于接收GoHeartbeat()发送的心跳信息/日志信息,后者用于接收GoElection()函数发出的投票申请信息。

2.2 注意问题:

首先是对于锁的使用,当可能会出现多个协程修改同一共享变量(例如节点的rf.Term就在多处被修改)的时候,就需要加锁。而如果一段代码可能会阻塞或者处于等待状态的话就不要加锁,例如自己在写GoElection()函数时出现的一个问题,Candidate状态节点会通过调用sendRequestVote(…)函数向其他节点发送投票申请,根据反馈结果来修改自己得到的票数votes,因为对于每个节点,Candidate都会单独开辟一个协程来处理上述过程,所以在对总票数votes进行判断前要先用time.Sleep()等一会儿,等到各个协程运行完后再处理votes值,此时如果在time.Sleep()前加锁,会导致 votes值无法得到修改,因此也就无法处理正确的votes值,所以一定是先写time.Sleep()语句,再加锁。

其次就是网络中一个节点向其他节点通信时一定不要用串行方法,要单独开一个协程来处理。

2.3 代码细节(对应第2.1节框架各个部分)

因为关键代码部分都加了注释,所以一些小的逻辑细节就不再赘述(主要就是根据接受信息/反馈信息的任期、当前节点/server自己的任期和状态来决定要不要接受信息或者更新自己的状态),这里先给出一个关键函数GoElection(…)的伪码。

算法2.1 GoElection(…)函数

表2.1 代码截图对应表

模块名

对应代码截图

Raft结构体

图2.1

RequestVoteArgs结构体

图2.2

RequestVoteReply结构体

图2.3

AppendEntriesArgs结构体

图2.4

AppendEntriesReply结构体

图2.5

Make(…)函数

图2.6

GoElection(…)函数

图2.7

GoHeartbeat(…)函数

图2.8

AppendEntries(…)函数

图2.9

RequestVote(…)函数

图2.10

图2.1 Raft结构体( server/节点的实体类)

图2.2 RequestVoteArgs结构体(封装了投票申请)

图2.3 RequestVoteReply结构(投票反馈信息)

图2.4 AppendEntriesArgs结构(封装心跳信息/日志信息)

图2.5 AppendEntriesArgsReply(心跳反馈)

图2.6 Make函数

 

图2.7 GoElection函数(开启选举的核心函数)

图2.8 GoHeartbeat函数(用于Leader发送心跳)

图2.9 接受心跳信息

图2.10 RequestVote函数(接受拉票信息,返回投票信息)

2.4 运行结果

图2.11 2A运行结果

3 日志复制模块

3.1 思路概括(代码角度):

日志复制模块的入口可以看成是提供的框架中的rf.Start(…)函数(除了Start函数,在Leader每次向其他servers发送心跳时,如果发现有发送日志的必要,也会进行日志复制操作),本次实验中,还另外定义一个GoCopyLogs(…)函数,在Start中,通过调用GoCopyLogs()来实现向其他server/节点发送日志的主要逻辑,日志信息接受函数和第1节中的心跳接受函数为同一个函数,只不过添加了对日志进行处理的代码。下面分别简要说一下发送日志和接受日志处理过程的思想。

发送方:

在发送日志信息时,一个很重要的变量为nextIndex[i],它表示Leader向Follower[i]发日志时,应该从哪个位置开始复制。其更新的地方有3处,第一个是Leader刚被选举出来时,此时可以更新所有follower的nextIndex值为len(leader.logs)+1;第二个是Follower复制日志成功时;第三个是Follower添加日志失败时,此时需要缩小nextIndex的值到Leader和Follower能匹配的地方。

发送方首先会把nextIndex开始的所有日志发给其他Follower,同时还会带上nextIndex之前一条日志的term和index,以判断之前的日志是否完全匹配。如果Follower第一次就成功复制了日志,那Leader只需要更新一些相应的Index即可,如果没有复制成功,则需要根据Follower反馈的冲突点重新调整nextIndex的值,再重新发送日志过去进行复制,这里冲突点的确定参考了论文里提到的优化策略,大概思想如下:

图 3.1 冲突点确定策略

接收方:

当接受方收到一条日志添加请求时,会进行如下逻辑判断:

图3.2 Follower处理接受日志的逻辑

3.2 注意点

这里在写代码时一直没考虑Make(…)函数中applyCh这个量的作用,以为只把日志信息添加到server的logs属性中就可以了,导致测试没法通过。对于applyCh这个量,目前的理解大概是,server通过applyCh把日志中的命令提交到状态机/执行命令的机器上,然后test.go文件测试时,测试的是执行命令的机器上有没有这条日志记录。

所以,为了实现提交功能,定义了一个新的函数GoApply(…),该函数在发现server的commitIndex大于lastIndex时,就会把相应的日志命令塞到applyCh中。另外和提交功能相关的几个Index的含义及更新时机如下:

matchIndex:每个Follower和Leader日志一致的最高位置。更新的地方有两个:一个是follower添加日志成功时要更新matchIndex,再一个就是通过Start(…)函数接受一个新的命令时,会更新leader自己的matchIndex,方便后面统计多少follower和自己日志一致,以判断要提交哪些命令。

commitIndex:目前已经提交的日志的最高index。在Leader添加日志后,会通过matchIndex[follower[i]]来检测某位置的follower是否超过总节点数的一半,是的话就把该位置的日志标记为提交状态。

lastApplied:和commitIndex的含义一样,lastApplied用于判断是否应该向状态机发送要执行的命令了。

3.3 细节代码

分别依次展示四处代码:GoApply(…)函数(提交命令到状态机)、GoCopyLogs(…)函数(Leader发送日志)、Start(…)函数(日志复制入口)、AppendEntries函数(follower接受日志)

图3.3 GoApply函数

图3.4 GoCopyLogs(…)函数

图3.5 Start(…)函数

图3.6 AppendEntries函数

3.4 运行结果

 

图3.7 日志复制模块运行结果

4 持久化模块

许多博客把持久化任务看作持久化和日志复制优化两个任务,因为前面在日志复制中已经考虑了优化策略,参考代码帮忙少踩了很多坑,所以这里就只关注哪些量需要被持久化、怎么实现持久化以及哪些地方需要调用持久化操作这三个问题就可以了。

首先,根据论文及其他资料,需要持久化的量有三个,term、votedFor以及log。其次,怎么实现持久话以及读取持久化,框架中已经给出了例子,照着写一下就行了,代码如下:

 

图4.1

其次,在哪些地方调用这两个函数。对于readPersist函数,仅需要在节点启动/初始化时调用一次就可以了;而对于persist()函数,则所有代码中,修改上述三个量的地方都应该调用一次persist()函数。

5 日志压缩/快照模块

图 5.1 交互图

通过参考上面5.1交互图以及一些资料和代码,将看的内容总结概括一下,在日志压缩模块的代码实现中,我们只需要关注如下三个问题即可:

  • 弄清安装一个快照需要包含哪些信息,定义安装快照相关的结构体。

图5.2 安装快照相关的实体类

  • 实现框架中给出的Snapshot(函数),上层服务会调用它更新快照及日志。

图5.3 Snapshot(…)函数

  • Raft层之间的快照信息传递:编写发送快照的代码、接受并安装快照的代码。

首先,当Leader节点发送给其他节点的日志信息被压缩/删除时,Leader需要发送快照给其他节点,所以,考虑在日志复制的函数中进行这一操作,思路如下(代码要特别注意压入日志压缩功能后对log的索引的使用,不能再直接使用绝对位置了,要做转化):

图5.4 发送快照代码

其次,需要编写接受快照的代码,定义一个InstallSnapshot(…)函数如下,这里需要说明的是,InstallSnapshot(…)函数中只是把要安装的快照信息塞入到applyCh中,提交到了状态机中,我们还需要完成框架中定义的CondInstallSnapshot(…)函数,来最终完成快照的安装。即快照经过的路径为:Leader发送快照-> InstallSnapshot函数->applyCh->CondInstallSnapshot函数,这么做的目的可能是为了保证状态机/上层服务和raft层在安装快照时是一起的,是具有原子性的,也可能有其他原因。当然,也有一些参考代码/博客将CondInstallSnapshot函数的返回值直接设置为true,好像这也是6.824课程所建议的方法,这里就先不考虑原因了。InstallSnapshot(…)函数和CondInstallSnapshot(…)函数代码如下:

图5.5  InstallSnapshot函数代码(Follower接受快照信息)

               

图5.6 CondInstallSnapshot函数代码(安装快照)

图5.7 运行结果截图

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

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

相关文章

[WMCTF 2023] crypto

似乎退步不了,这个比赛基本不会了,就作了两个简单题。 SIGNIN 第1个是签到题 from Crypto.Util.number import * from random import randrange from secret import flagdef pr(msg):print(msg)pr(br"""........ …

WMS仓库管理系统选择指南:如何确保您的仓库提高效率?

如何选择WMS仓库管理系统?仓库管理主要包括以下四个方面: 1.商品出入库管理 2.库存调拨 3.库存盘点 4.虚拟库存/实际库存管理 为了更好地管理仓库,我们需要确保基本的硬件设施得以满足,例如划分存储区域、使用货架以及进行员工培训…

Vivado2018.3版本_编译下载打包固化程序

Vivado2018.3版本_编译下载打包固化程序 概述: 在Vivado中开发导出硬件平台,然后在SDK中进行C语言的开发工作,然后把SDK编译生成的.elf文件加入Vivado工程中,编译生成.bit文件,转换成.mas文件,就可以固化到…

使用kubeadm工具升级kubernetes

一、背景: kubeadm部署的kubernetes集群进行升级,通常先升级控制节点,控制节点升级完成后再升级工作节点,本博文只升级了控制节点,工作节点按照相同的流程进行升级即可 环境说明: 主机名节点11.0.1.200k8s…

机器学习笔记之优化算法(十五)Baillon Haddad Theorem简单认识

机器学习笔记之优化算法——Baillon Haddad Theorem简单认识 引言 Baillon Haddad Theorem \text{Baillon Haddad Theorem} Baillon Haddad Theorem简单认识证明过程证明:条件 1 ⇒ 1 \Rightarrow 1⇒ 条件 2 2 2证明:条件 3 ⇒ 3 \Rightarrow 3⇒条件 1…

功率放大器的介绍和应用领域有哪些

功率放大器是将输入信号的功率放大到输出端所需水平的一种电子设备。在实际应用中,功率放大器被广泛使用于音频、视频、通讯等领域。下面,安泰电子将详细介绍功率放大器的工作原理、类型以及应用领域。 功率放大器的工作原理是将输入信号的功率放大到输出…

js 简单的页面导航

实现效果: 实现原理: 实现原理是通过创建数组里面包含对应页面的信息,当点击的时候为数组传递一个对象,里面包含要显示的信息。 跳转链接实现原理: 通过创建一个数组,里面包含所有可以跳转的页面链接名称&a…

【3Ds Max】可编辑多边形“边界”层级的简单使用

目录 示例 (1)挤出 (2)插入顶点 (3)切角 (4)利用所选内容创建图形 (5)封口 (6)桥 示例 这里我们首先创建一个长方体&#xff…

3D角色展示

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>3D卡片悬停</title><style>font-face {font-family: "Exoct";src: url("htt…

解决群晖视频信息插件无法同步的问题

今天发现群晖下载的电影和电视剧没办法显示图片和相关信息&#xff0c;开始以为是升级了什么东西导致的&#xff0c;后来发现api.themoviedb.org 这个API接口ping都没法ping通&#xff0c;估计被墙了。好吧&#xff0c;重新登陆群晖修改hosts文件吧。下面一步一步来讲讲如何修改…

Adobe联创去世,没他就没有PDF,乔布斯也因他逆风翻盘

量子位 | 公众号 QbitAI Adobe联合创始人John Warnock去世了&#xff0c;享年82岁。 他是PDF格式的发明人之一&#xff0c;贡献了PDF中的核心技术PostScript—— 如果没有它&#xff0c;打印机就无法打印复杂页面。可以说&#xff0c;Warnock的这项发明&#xff0c;在上世纪…

【C++】STL——set的介绍和使用、set的构造函数、set的迭代器、set的容量和增删查改函数

文章目录 1.set的介绍2.set的使用2.1set的构造函数2.2set的迭代器2.3set的容量函数2.4set的增删查改函数 1.set的介绍 set的介绍 &#xff08;1&#xff09;set是按照一定次序存储元素的容器。 &#xff08;2&#xff09;在set中&#xff0c;元素的value也标识它(value就是key…

LTDC之外部SDRAM

1.配置外部SDRAM&#xff08;嵌入式基础知识&#xff0c;此处不做分析&#xff09; 2.编写SDRAM配置代码&#xff08;copy正点原子例程&#xff09; sdram.c#include "sdram.h" #include "fmc.h"uint8_t SDRAM_Send_Cmd(uint8_t bankx,uint8_t cmd,uint8_…

区块链与算力网络:创造未来网络的安全与共享

在数字革命的浪潮下&#xff0c;网络技术正焕发着前所未有的活力&#xff0c;而算力网络以其独特的区块链技术应用&#xff0c;为网络的安全性和资源共享带来了新的可能性。本文将带您深入探索算力网络中区块链技术的神奇应用&#xff0c;为您呈现这个充满活力和创新的网络未来…

全方位介绍SRM系统供应商采购管理

SRM系统是一种用于管理与供应商合作关系的解决方案。本文将全面介绍SRM系统供应商采购管理的概念、重要性、功能模块以及应用优势&#xff0c;以帮助企业了解如何通过SRM系统提升采购管理效率和供应链合作关系。 一、SRM系统供应商采购管理概述 1.1 什么是SRM系统供应商采购管…

19 真实的战场:如何在大型项目中涉及GUI自动化测试策略

大型全球化电商网站 GUI 测试的策略设计 组件 -> 模块 -> 端到端 1&#xff09; 首先&#xff0c;要从前端组件的级别来保证质量&#xff0c;也就是需要对那些自定义开发的组件进行完整全面的测试。通常前端组件会基于 Jest 做比较严格的单元测试。 Jest 是由 Facebook 发…

内网渗透神器CobaltStrike之Profile文件编写(十)

简介 在网络渗透测试和红队模拟攻击中&#xff0c;隐藏命令与控制&#xff08;C2&#xff09;流量的特征对于成功绕过入侵检测系统至关重要。Cobalt Strike提供了一个强大的工具——Malleable C2 profiles&#xff0c;用于自定义C2流量&#xff0c;从而使其看起来像正常的网络…

docker搭建opengrok环境2

引言&#xff1a; 虚拟机关闭后重新开启&#xff0c;理论上是需要重新启动一下docker的&#xff0c;以重新启动其中的服务。 命令基础&#xff1a; docker images&#xff1a;查看docker中现有的镜像 docker container ls -all&#xff1a;查看docker中目前在运行的containe…

Centos7 交叉编译QT5.9.9源码 AArch64架构

环境准备 centos7 镜像 下载地址&#xff1a;http://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/ aarch64交叉编译链 下载地址&#xff1a;https://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/aarch64-linux-gnu/ QT5.9.9源代码 下载地址&#xff1…

现货白银投资什么的?

也许很多投资者听说过现货白银&#xff0c;但并不知道它投资的是什么&#xff0c;过程中是如何进行买卖的&#xff0c;也不知道如果参与其中&#xff0c;自己需要承担什么风险&#xff0c;最终的收益会如何。对于上述的这些问题本文&#xff0c;将为大家简单地介绍一下。 虽然现…