Talent Plan 2.0 TinyKV 总结&建议
完成情况
我是在大三的寒假完成本项目,用时跨度3个月,实际投入天数2个多月,仍有部分瑕疵(没有通过全部测试,Project3B
最后一个测试函数大概率过不了,总共有三四种不同的报错,实在调不通了),但最终很意外的拿了95分。
思路分享
以下是对项目任务的概述,掺杂我个人的理解以及辅助完成项目的一些链接,比较简略,如果有什么错误,请帮我指出。
Project1
本部分旨在实现Standalone Storage
接口和Raw Key/Value service
提供的四种基本操作,需要实现的代码位于kv/server/server.go
和kv/storage/standalone_storage/standalone_storage.go
文件。
Project2A
本部分实现了Raft算法中的领导人选举、日志复制以及Raft
对外的Rawnode
接口。
- Raft论文
- 阅读
raft/doc.go
获得更多的项目相关的细节描述(注意,本项目中的实现要和论文中的描述有一点不同,具体的不同只有通过面向测试编程才能发觉)
- 参考实现etcd,找老版本的看(实现思路类似)
- 本部分在之后的部分中会反反复复地修改,代码文件也会变得十分的长,所以建议在一开始就将
raft.go
拆开,分成领导人选举相关、发送消息相关、处理消息相关、结构体定义相关的四部分代码。
- 记得使用
log/log.go
给工具来打日志,比方说结点新建或者重新恢复之后的内部变量的值、选举的消息、节点状态变更情况等等,反正哪里有bug打哪里(不能调试的痛)
- 实现注意第一点:注释上说领导人上任之后要
propose
一个空entry
,但任务书里用的是append
,目前来看还是propose
是对的。
- 实现注意第二点:选举人获得多数反对票,直接变回跟随者。
- 实现注意第三点:每次状态变更的时候随机选举期限,如果只是在选举之后随机下一次选举期限,会导致冲突率较高,过不了其中俩测试(测试要求冲突率在0.3以下)
- 实现注意第四点:领导人在检测到
Commit
更新之后要广播AppendEntries
,不是HeartBeat
。
Project2B
本部分实现了Peer
层中对四种普通消息的支持,本部分主要依据Peer
层对消息的处理流程来进行实现。
Raftstore
层中raft_worker
收到的消息,交给peer_msg_handler
进行处理。
peer_msg_handler
在拿到raft
消息之后,进一步交给下一层Rawnode
进行propose
,此时将回调函数保存至proposal
中。这一部分要实现kv/raftstore/peer_msg_handler.go
中proposeRaftCommand()
函数,因为之后要处理Admin
的消息,所以不要直接在这个函数里面写,注意分层。
peer_msg_handler
对Raft(Rawnode)
层消息处理完成(多数成员确认)后的结果Ready
进行处理,即实现kv/raftstore/peer_msg_handler.go
中HandleRaftReady()
函数以及kv/raftstore/peer_storage.go
中SaveReadyState()
函数。这里就实现任务书里的伪代码就好。
- 如果不做异步Apply优化,这里直接在
peer_msg_handler
中实现相关的Apply
操作即可,也就是在HandleRaftReady()
函数中进行。
- 如果要做,则要考虑很多的方面,需要更深入地阅读代码:
kv/raftstore/raft_worker.go
:学习raft_worker
咋写的(他的注释暴露了一些实现),也就是说,raft_worker
应当管理另一个worker
来异步处理多数确认的CommittedEntries
。
kv/raftstore/message/msg.go
:查看一下消息定义,因为异步处理免不掉消息传递,所以要自己来设计传递消息的类别。
kv/raftstore/peer_msg_handler.go
:查看handler
的具体实现,主要是从HandleMsg()
函数看起,因为异步处理之后,Apply
消息和结果都是要进行考虑的。
kv/raftstore/router.go
:查看本层次中消息是怎么发送的。
kv/raftstore/raftstore.go
:查看各个worker
是如何启动和关闭的。
- 根据以上的代码阅读,设计自己的异步处理
worker
就好啦。
- 这部分的测试代码应该是有比较严重内存泄漏(
捂脸),我虽然没有找到bug在哪,但是我写一个shell脚本,一个测试一个测试去跑,算是临时解决了这个问题吧23333。
Project2C
本部分实现了Raft算法中的日志压缩功能,该功能是为了防止Raft
层的日志无限扩展,一些已经committed
的日志可以压缩并保存在数据库中,而不是一直留在内存中。
- 理解一下日志压缩是怎么工作的。
- 一方面要修改
Raft.go
和log.go
,实现对Snapshot
的支持。这里一定要搞清楚log.go
里面变量的关系snapshot/offset.....applied....committed....stabled.....last
,如果这里设计不好,之后的测试会出现奇奇怪怪的错误,然后心态炸裂(我就是这样重写的orz)。
- 因为之前日志都在内存里面,所以就是
RaftLog.entries
这个切片里面,下标和日志的index
基本是对应的。
- 加入了
Snapshot
之后,我觉得最好添加一个offset
,毕竟它注释里给了,方便进行上述下标的转换。
- 这里每一个函数都要考虑清楚越界的问题。
- 这里针对
Snapshot
的接收要考虑清楚要更新以上哪些变量。
- 另一方面要修改
Rawnode
中对Snapshot
的检查,因为Ready
中有Snapshot
一项,需要在Peer
层进行落实。
Peer
层要实现对CompactLog
消息的支持(propose
和apply
),在获得apply
结果之后peerMsgHandler
需要再发送消息给RaftLogGCWorker
。最好判断一下到底要不要GC
,不然会有一连串的no need to gc
。。。
- 注意保存节点的各个状态,主要是
applyState
、raftState
、snapState
,用meta
里面的函数。
- 这个地方要是出现测试跑不动(卡死)看这个issue。
Project3A
本部分实现了Multi-Raft中Raft
层对领导变化和配置变化的支持。实现主要参考任务书的描述,比较简单。
Project3B
本部分实现领导变化、配置变化、Region
分裂三个功能。本部分是最难的,我的实现主要参照任务书的描述和etcd
的server
实现,但实现的还是有未知的问题,所以这里只简单说一下和注意事项。
PeerMsgHandler
:对三个功能对应的命令进行propose
(领导变化不用Apply
),同时增加根据Apply
的结果进行状态信息的更新与保存(主要是Region
和Peer
的信息,同时领导可能还要启动新的Peer
啥的)。
因为要进行add
和remove
,所以节点的状态要考虑在内,过期的消息要清理并通知。
Region
分裂的时候要计算一下大小的差,这里注意类型,peer
保存的是无符号的,估算的时候得是有符号的。
因为Region
分裂之后,普通的操作要对Key
进行一个判断,这里我还没搞懂在哪里判断,是在propose
还是apply
,所以我都写了。
Region
分裂之后,要分清楚怎么裂开的,哪一个是新的,哪一个是旧的,领导人节点要处理新peer
的创建。
MultiRaft论文
Project3C
本部分实现了Region
的均衡调度,主要是为了更加合理地调用3B
中实现的功能。实现过程主要参照任务书中的描述就行了,挺详细。
Project4
本部分代码实现了面向Clients
的Server
层,主要是实现服务器的事务系统API以及保证SI特性的MVCC。任务书写的很泛,主要看kvrpcpb.proto
文件的注释,过程都描述了一边,照着实现即可,以下是一些帮助理解事务模型的文章。
这一部分接口感觉设计的很奇怪,最后实现完觉得处理的很暴力,重复的代码很多,我感觉这里应该是要统一把消息交由一个类似于Handler
的东西来处理,等进一步的优化吧。
最后建议
在参与本项目之前,我得到的消息是说要在五六周内完成,但事实证明,对于零基础的同学来说时间太紧张了(一半都做不到)。
如果你是零基础,希望你能在课下学习相关的前置知识,学习Golang基本语法和并发编程相关知识,稍微了解一下Raft和gRPC。
如果你接触过Golang,学习过6.824课程,阅读过Raft论文,那么你可以即刻着手该项目(对于大佬一个月就够了/膜)。
我在参与这门项目之前写过一段时间Golang,算是零基础了,以下是我的具体进度(除去摸鱼的时间),仅供参考,但反正慢慢来,不要急,一定可以取得好结果的。
- 2天:Golang语言基础复习
- 2天:Golang并发编程相关
- 1天:gRPC相关知识
- 1天:完成Project1
- 7天:阅读Raft论文,完成Project2A
- 14天:完成Project2B
- 7天:完成Project2C(试图完成,实际有很多很多Bug)
- 7天:学习MultiRaft,完成Project3A与部分Project3B(在此之后算是寸步不行)
- 5天:分析至此的全部代码,Debug!
- 1天:重写Project1
- 12天:重写Project2
- 4天:重写Project3A与3B(仍有bug)
- 3天:完成Project3C与Project4
- 1天:分析、查错、添加注释
如果你想挑战该项目,在此之前的除知识之外的其他准备:
- 该项目对硬件要求还是蛮高的,电脑还是尽量16G内存,不然跑起测试来很痛苦。
- 不要单打独斗,容易进坑,得抓几只志同道合的小伙伴,长时间单独无法解决的问题不要一直一个人死扣。
- 不要一味地去读源码,要有选择的读,不要妄想一下子搞懂一切,何况有些部分的代码确实写得复杂晦涩而且对项目理解没有实质性的作用。
- 记得写日志,每天投入到该项目上的时间,学了什么,干了什么,做了什么优化,尽可能地记录下来,最后提交的时候文档也算在评分标准中哦。
- 做好心理准备,开始之后就不要轻言放弃。