MIT6.824 Lab4 实现及解析
目录
实验目的
lab4需要实现两个模块:
- lab4-1 完成一个基于KvRaft的ShardMaster(可以理解为一个分片调度的存放的机器),但是写入的是配置而不是简单的K-v的值(但是与Lab3的实现相当相似)
- lab4-2 完成一个Sharding的kvRaft数据库(实现了Multi-raft)
整体的实验架构是这样的
架构图如上:
一个值得注意的点是,各个KV数据库层之间不会直接相互通信,只是会通过Raft层来同步操作。
并且Client如果找到的不是Leader的节点,会直接放弃操作。然后请求集群内的下一个节点。
PS: 这里的KvDatabase 和Raft总体包起来才是一个KVServer的实例,而不是单独脱离的。
流程大概如下:
- Client发送请求到ShardMaster,查询Key的具体对应的分片在哪个组里面
- ShardMaster收到请求后,返回对应数据Key所在的组信息
- Client发送请求到对应分片的Raft Leader中
- Raft层同步成功后,会通过ApplyCh返回信号的KvDatabase
- 分片的KvDatabase对Client端做出应答(如果成功返回结果为成功,如果下面同步层失败导致超时,返回给客户端的结果为超时)
副本与分片的问题
在上面的架构图中,Multi-Raft已经实现了分片的副本的实现。
分片: 只要是把数据进行划分(从大的数据变为只负责一部分的数据量)即是分片(在定义上面数据库的垂直划分和水平划分和此处的Key按键去Mod划分都是属于分片的操作,但是数据库表的划分和此处的Key的Mod划分不是在同一个层级上面的,理解不一样)
副本: 是指数据的重复的数量。但是一般只有一份的数据我们不会称之为单副本,而是称为0副本。副本一般是>=2才叫。副本的目的是为了冗余的问题。防止因为单点故障而导致数据全部的丢失。
实验实现
lab4-1
对比起与Lab3 的实现,它在这里需要支持的是Leave(), Move(), Join(), Query()的四种方法,因为分别对应节点的加入集群、退出集群、移动集群和集群配置查询的四种操作。
所以基本实现的思路与Lab3是类似的。
1 | type ShardMaster struct { |
但是有一个比较重要的需求是,它需要完成一个shard 与 集群的绑定关系的变动,因为本来就是需要支持的目的就是数据随着Group的添加和变动来做到数据的均衡。
并且一个比较不同的是,对于重复的Get操作操作,Lab3采用的是直接返回Kv的值,但是在此处,因为Config的存放机制是一个数组(里面的顺序就是配置有效的顺序),因此需要把重复的读配置,返回一个复制好的配置。
lab4-2
这里的实现是需要首先保证Kv的功能可以使用,然后保证在配置变动并且数据搬移完成之后,才能继续对外提供Kv的服务。并且需要保证节点挂掉之后可以读取会最新的状态下来
总体流程:
在提供KV服务的同时,需要把配置定时进行更新,并且实际应用新配置之前,必须保证数据迁移成功。因此实际上用到了3个单独的协程去分别做这几个工作
- 读取配置协程(定时向ShardMaster请求配置)
- 数据迁移的协程
- 应用数据同步的协程
因为这个实验中的目的有三种,因此我们的消息类型也定义了三种
- 数据操作类型,与Lab3原来类似的OP类型(可以包含Get、Pull、Put、Append的操作)
- 配置更新类型,把Lab4-1的Config类型封装一层进行使用
- 真实的数据迁移类型,原因是:因为数据迁移的时候是两个RaftGroup的Leader相互通信,并且需要把原来数据KV格式同步进去到新的组的所有副本上面。因此单独分配一个数据类型来记录此类数据
向ShardMaster读取配置的协程的实现
1 | func (kv *ShardKV) ConfigUpdateRoutine() { |
修改自己需要发送和接受Shard的配置部分的代码
1 | case Cfg: |
获取迁移的数据部分
数据迁移的副本是只要检测到相关属性的变化之后(感知到数据的变化)后,新的数据所归属的Leader就会与旧Leader继续RPC的Pull调用, 去获取它的Database和DUP的部分
当拉取到配置了之后,就会把数据变成日志应用到状态中,就可以实现分片数据的副本的性质。
1 | func (kv *ShardKV) MigrationRoutine() { |
非Leader节点同步KV数据的方法
1 | case Migrate: |