6.824

module
v0.0.0-...-e8e7e21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2020 License: MIT

README

MIT 6.824分布式系统实验课程代码(2018)

MIT 6.824分布式系统课程,是一门著名的讲解分布式系统设计原理的课程。

官方网站http://nil.csail.mit.edu/6.824/2018/index.html

包括4个Lab:

说明: Lab1是MapReduce,需要在基础代码上补全MapReduce框架,并实现Map函数和Reduce函数; Lab2是Raft算法的实现,需要根据Raft论文完成Raft算法 Lab3是分布式kv存储服务的实现,基于Lab2中的Raft实现分布式kv存储服务的客户端代码和服务端代码 Lab4是分片的分布式kv存储服务的实现,基于Lab3中的分布式kv存储服务进行扩展,分别实现shardmaster(管理shard-group映射关系)和shardkv(需要实现shard迁移)

Lab1比较独立,Lab2 3 4是逐步增加难度并且后一个实验依赖于前一个实验。

实验中用Golang协程模拟分布式中的节点,节点间只通过rpc通信(rpc模拟了网络不稳定情况)。

基础代码中包含了Lab的框架和比较严格的测试代码,但是由于lab2 3 4实验比较复杂时序不确定,某些极端bug还是需要反复测试才能复现。

各部分理解和说明:

Lab 1: MapReduce

Part I 实现MapReduce输入输出框架(doMap、doReduce两个函数)。 Part II-V 根据条件分别实现一组mapF和reduceF函数。

结果:

Lab 2: Raft

extended Raft paper

分布式一致性算法:Raft 算法(Raft 论文翻译)

1. 全局需要注意的

  • 收到rpc心跳请求包含term高于自己,立刻身份转变为follower,并把自己term更新为请求中的term
  • 所有关键点的rpc响应(投票,commit新log,收到同步的新log,装载镜像),需要先持久化再响应
  • Data Race问题:修改raft中的状态,必须加锁
  • Data Race问题:日志slice只能append,若删除部分日志必须创建新slice,再把旧的需要保留的添加进来。

2. Leader选举关键点

涉及三个参数:当前节点的term,当前节点最后一条日志的信息lastLogIndex和lastLogTerm

给其他节点投票的必要条件:

  • myTerm <= term (别人的term大于等于我的)
  • myLastLogTerm < lastLogTerm || myLastLogTerm == lastLogTerm && myLastLogIndex <= lastLogIndex(别人的日志比我的新)
  • 在当前term中没给其他节点投票(一个term中只能投票一次)

只有两种节点可以成为Leader:(Raft论文 5.4.1 Election restriction)

  • 在最大term中接收过log的节点
  • 在相同term中接收更多log节点

candidate竞选失败的4种情况:

  • 某个节点失联,剩余节点竞选导致投票数恰好平均,竞选超时,开启下一轮竞选
  • 大部分节点失联,无法达到超过一半的投票数,竞选超时,开启下一轮竞选
  • 收到投票响应,对方term大于自己,更新自己为对方term。若自己的日志比对方新,开启下一轮竞选;否则转变为follower退出竞选
  • 当前身份已经不是candidate,退出竞选(收到rpc心跳请求大于等于自己的term,发现已存在的leader,转换为follower)

3. 如何commit log(关键点)

3.1 Raft分布式事务的两个阶段

committing阶段:leader向其他节点同步自己的日志实际上就是发起一个提交事务(隐含在逻辑上发起了committing)

committed阶段:大多数节点上已经同步成功了某个index的log,并且这个log的term是leader的term,那么此时该log已经committed(无论leader和其他节点有没有来得及更新commitIndex)

3.2 为什么大多数同步成功且log的term等于leader的term就是完成了committed阶段?

事务提交成功的本质:提交成功的那条log已经永久的保留在集群中(这条log的index和内容不会改变)。 一旦上面2个条件符合,即使后续leader挂了,新选出的leader也必然是已经同步了这条log的节点(虽然新选出的leader不能立刻committed这条日志)。

假设最开始s1是leader,此时s1挂了,那么竞选成功的只可能是s2和s3(给别人投票的必要条件之一:别人的日志比我新)
s1 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s2 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s3 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s4 commitIndex = 1 [{term:1,index:1,command:"xxx"}]

s5 commitIndex = 1 [{term:1,index:1,command:"xxx"}]
          

3.2 为什么leader不能committed不属于自己term的log?

如果之前的leader(这条log的term对应的)如果已经把这条日志复制给了大多数,那么这条日志是committed; 而如果之前的leader没有复制给大多数,那么这条日志不是committed的。

当前的leader不能确认之前leader的状态,所以不能确定这条log是不是committed(即使现在已经复制给了大多数节点,也可能被覆盖)

假设最开始s1是leader,只有s1和s2上有index=2的log,此时s1挂了
s1 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s2 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s3 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"}]

s4 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"}]

s5 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"}]

因为s3 s4 s5投票,leader的成为了term=3的leader,并接收一个新log

s1 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s2 term = 1 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s3 term = 2 commitIndex = 1 [{term:1,index:1,command:"xxx"}]

s4 term = 2 commitIndex = 1 [{term:1,index:1,command:"xxx"}]

s5 term = 2 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:3,index:2,command:"xxx"}]


此时s3挂了,s1重新成为leader,s1-4都可以投票,然后s1继续把自己日志复制给s3-4

s1 term = 3 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s2 term = 3 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s3 term = 3 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s4 term = 3 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s5 term = 2 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:3,index:2,command:"zzz"}]

假如此时s1认为index=2的log已经committed更新自己和s2-4的commitIndex
s1 term = 3 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s2 term = 3 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s3 term = 3 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s4 term = 3 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s5 term = 2 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:3,index:2,command:"zzz"}]


然后s1挂了,s5此时竞选成功,由于日志比别人新
s1 term = 3 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:1,index:2,command:"yyy"}]

s2 term = 4 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:4,index:2,command:"zzz"}]

s3 term = 4 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:4,index:2,command:"zzz"}]

s4 term = 4 commitIndex = 2 [{term:1,index:1,command:"xxx"},{term:4,index:2,command:"zzz"}]

s5 term = 4 commitIndex = 1 [{term:1,index:1,command:"xxx"},{term:4,index:2,command:"zzz"}]

此时就出现了逻辑矛盾,已经committed的log被新leader给覆盖了,所以leader不能committed不是自己term的log      

3.3 leader如何提交不属于自己的term的log

当前leader在自己的term成功committed了log,那么这条log之前的所有log都是committed

3. 如何加载/保存snapshot(3B)

什么是snapshot snapshot即快照,指某一时刻的状态。例如一个map某时刻它的k-v键值对就是一个快照,{a:1, b:2}

log和snapshot什么关系? 这里的log是指某些操作或者命令,例如:现在的map快照 = 过去的map快照 + 中间发生的操作

{a:10, c: 1} = {a:1, b:2} + [动作1删除k:b + 动作2添加kv:(c:1) + 动作3修改kv(a:10)]

Raft中的状态机就相当于一个map,就是一系列log中包含的命令的运算结果,而snapshot就是某一个时刻的运算结果

new snapshot = command in logs(all)

new snapshot = old snapshot + command in logs(from old to new)

保存最新的snapshot状态,可以用过去的snapshot加上中间的log,这样可以节省存储空间。

所以当Raft状态机成功执行了指令后,可以保存一次当前的快照,这样快照之前的logs就可以都删除

4. 如何处理网络不可靠情况

rpc请求死循环,直到获得请求结果或身份发生转变退出循环

5. 代码逻辑架构图

6. 结果:

Lab 3: Fault-tolerant Key/Value Service

基于Raft实现分布式kv,实现Raft论文中的状态机部分和snapshot的保存和加载

代码逻辑架构图:

结果:

Lab 4: Sharded Key/Value Service

基于分布式kv,实现分片的分布式kv

代码逻辑架构图:

shardmaster结果:

shardkv结果:

Directories

Path Synopsis
src

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL