(2022)
MIT 6.824 分布式系统Lab1-MapReduce
/mr 实现思路
coordinator和多个worker通过RPC通信,worker以plugin方式加载/mrapps下的Map()和Reduce()函数实现
worker向coordinator发心跳要任务:
- 若是Map任务,收到fileName读取文件kv,mapf处理后输出成json格式的中间文件mr-fileId-reduceId
- 若是Reduce任务,将mr-XX-reduceId文件合并后输出mr-out-reduceId
coordinator管理整体任务状态:
- 在Map全部完成后才开始Reduce
- 给worker任务10s后检查完成情况,没完成就重新标记为待做
Lab2-Raft
Part2A 选举和心跳
- currentTerm作为逻辑时钟,空entries[]的AppendEntries RPC作为心跳
- 三个角色follower/candidate/leader的状态机转化
- 无论什么角色,收到的request或reply中对方的term比自己大,马上变成follower
粗略过程如下:
- 刚开始都是follower,超时没有收到leader心跳变为candidate
- candidate广播RequestVote RPC请求投票
- RequestVote处理方对一个term只能投一张票
- candidate收到">1/2多数"选票变为leader
- leader广播AppendEntries RPC心跳阻止其他选举产生
Part2B 日志项的复制
粗略过程如下:
- client用Start()发送需本地状态机执行的command
- leader在本地日志log[]中添加LogEntry{需本地状态机执行的command, 创建时的term}
- 通过AppendEntries心跳通知所有follower复制log[]
- follower[i]回复成功后,leader更新matchIndex[i],matchIndex[i]是已成功复制到follower[i]的log[]最大索引
- 用matchIndex[]找出满足">1/2多数"的commitIndex,则log[:commitIndex]已成功复制到多数follower,可应用log[:commitIndex]到本地状态机
- leader的commitIndex在心跳中传给follower,follower也可应用log[:commitIndex]到本地状态机
第3步通过心跳复制leader日志到follower[i],关键是找到leader与follower[i]日志匹配的最大索引lastMatch,然后将follower[i]的log[lastMatch+1:]全部覆盖。leader用prevLogIndex从最后位置(nextIndex[i]-1)往前探查lastMatch。日志匹配,这里检查某index位置的term相同。
Part2C 持久化
TestFigure8Unreliable2C的超时问题:
- 加速查找lastMatch的回退优化
测试帮助:
- go-test-many可多核运行测试
go-test-many.sh 20 8 2A // 共20次用8核运行`go test -run 2A`
- 死锁分析:
// 用deadlock.Mutex换掉sync.Mutex声明
go get github.com/sasha-s/go-deadlock
- pprof性能分析:
go test -race -run TestFigure8Unreliable2C -cpuprofile cpu.prof -blockprofile block.prof -mutexprofile mutex.prof
// 命令行
go tool pprof cpu.prof
top15
// web端
go tool pprof -http=":8080" cpu.prof
Part2D 日志压缩
论文Section7有介绍。
粗略过程如下:
- 上层应用调用leader的
Snapshot(lastIncludedIndex, snapshot)
传入快照。leader把log截断成[lastIncludedIndex:],log[lastIncludeIndex]留作dummy头 - leader在发送心跳时若发现
nextIndex[i]<=lastIncludedIndex
,改成InstallSnapshot RPC向follower[i]发送快照 - follower收到快照时截断自己的log,再往applyCh应用快照
Lab3-基于raft实现的容错KV服务
Part3A 无快照功能的KV服务
- kvclient向leader-kvserser发Get/PutAppend请求,leader-kvserver转发请求给自己的leader-raft层,leader-raft层在后台与follower-raft同步日志,然后leader-kvserver从applyCh确认收到请求,执行请求并将结果返回kvclient。
- kvclient的请求要同步返回,每个请求要等在leader-kvserver一个channel上,而leader-kvserver直到从applyCh拿到请求才能执行,执行结果与等待的请求之间需要有一(applyCh)到多(等待在opResultCh)的分发。kvclient请求由RequestId{clientId,seqNum}唯一确定。
- kvclient写请求可能因网络乱序到达。leader-kvserver要记录各kvclient已执行写请求的lastSeqNum,只执行
seqNum > lastSeqNum
的写请求。
Part3B 带快照功能的KV服务
kvserver加上快照的保存和恢复功能
Lab4-分片的KV服务
概念:
- 分片shard即kv子集
- 一个raft组(replica set)是一台逻辑主机,对指令线性一致排序,负责某些分片数据。leader接收指令,follower通过applyCh更新本地状态
- 配置config保存:分片在哪个raft组,组内有哪些服务器
- shardctrler管理配置,本身也是个raft组
Part4A 配置管理shardctrler
实现类似lab3B,支持操作:JOIN(增加raft组)、LEAVE(去掉raft组)、MOVE(将分片分配给某raft组)、QUERY(获取某版本的配置)
rebalance:分片再平衡按尽量平均的原则计算各组应有多少分片,多的先拿出分片存到数组unalloc,少的再从unalloc拿
Part4B 分片服务器shardkv
- 在lab3B的基础上修改
- kvserver修改本地状态机的操作都要通过raft同步
kvclient的过程如下:
- kvclient将key哈希成shard号,查shardctrler配置得到replica组gid,向gid的多个服务器轮发,直到发往组leader
- kvserver收到请求,若请求的分片数据不是自己负责的了,就返回ErrWrongGroup
- kvclient收到ErrWrongGroup,向shardctrler获取配置后重试
kvserver的分片迁移:
- 某分片迁移时不应该影响其他分片的服务
- 迁移状态是关于分片的,不是关于kvserver的
- 分片迁出迁入期间拒绝服务,迁出方返回ErrWrongGroup,迁入方返回ErrShardMigrating让kvclient稍后重试
- 怎么开始分片迁移?
- 由kvserver-leader定时拉取下一配置,并与kvserver-follower同步。若当前正在分片迁入或迁出,则等迁移完成再拉取。
- 若分片配置有变动,设置shardState[i]为迁入或迁出状态,把要迁出分片参数用通道发往migrateLoop做迁移。
- 迁移双方的配置版本相同才能迁移。如果迁出方版本>迁入方,稍后重试,等待迁入方版本追上来;如果迁出方版本<迁入方,迁入方已收到过分片。
- 宕机重启后shardState[]可以从currentConfig和lastConfig比较得到,再重发一遍迁移。
- 选push还是pull?我选了push实现
- push:config更新时kvserver-leader向对方raft组的leader推送对方缺少的分片数据
- pull:config更新时kvserver-leader向对方raft组的leader拉取自己缺少的分片数据
- 迁出RPC成功后,向raft记入GC操作,在applyLoop处理GC时删除迁出分片中的key
改/shardkv的bug
Bug:在Append进raft日志、Append日志处理之间插入了分片迁移,导致Append结果没随着迁移
协程1:== 1.1 判断分片没在迁移 ======= 1.2 Append进raft日志 ================== 1.2 Append日志处理
协程2:2.1 判断分片没在迁移 ==== 2.2 Config进raft日志 ==== 2.3 Config日志处理,分片迁移
把条件判断移到处理applyCh消息时,Append不负责分片就返回ErrWrongGroup,让kvclient重试
Bug:follower有时丢了日志没执行,shardState有时飘忽不定
kvserver要加个lastApplied字段,防止load了旧快照或load快照后执行了旧日志
Bug:TestConcurrent1重复Append了
applyPull()刚迁入分片,马上遇到kvclient之前轮询的Append
分片迁移时一起带上dedup map
Bug:TestConcurrent2在Append某key时分叉了
raft组重新选举,新主重放日志时重发push(),发来旧状态
applyPull()要根据gid去重
Bug:TestConcurrent3有一定几率卡住
有些调用由leader发起:shardState状态变更时向外push(),迁入切片后
go donePull()
通知迁出方。但此调用如果刚好发生在raft换主的短暂间隔中,将没人调用,流程卡住。
若不是leader,重试调用几次