一、系统设计目标
高可用的大文件分布式文件系统。
二、关键点
- Architecture-架构:有哪些Server,每个Server的功能。
- Master存储的MetaData的结构。
- client写数据流程。
- client读数据流程。
- 启动时Master和ChunkServer的交互。
- ChunkServer发生故障时,Master和ChunkServer的交互。
三、核心设计概述
2. Master中存储的MetaData
class ChunkServer { // ChunkServer在Master中的数据结构
string host; // ip+port信息 回复给client 用于数据读取
int64 lastHeartBeat; // 上一次心跳的时间戳
}
map<int32, ChunkServer> chunkSvrInfo; // chunkSvrId ==> ChunkServer
class Chunk { // 文件中数据块的数据结构
int64 handleId; // 该数据块的唯一标识Id
list<int32> chunkSvrId; // 存储该数据块的chunkSvrId
int64 checkSum; // 该数据块checksum的结果 客户端读取数据时可根据checkSum校验读取的数据是否被破坏 不一定存在Master中 可在ChunkServer中
}
class File { // NameSpace中文件的数据结构定义
string name; // 文件名
vector<Chunk> chunks; // 顺序存储的chunk数据
int64 createTime;
int64 updateTime;
}
class Directory { // NameSpace中目录的数据结构定义
map<string, Directory> dirs; // 该目录下的所有子目录
map<string, File> files; // 该目录下的所有文件
}
// handleId到具体Chunk的映射关系 ChunkServer只存储了Chunk对应的handleId
// 数据迁移时需要根据handleId找到对应的Chunk做出修改
map<int64, Chunk*> handleMap;
Master持久化MetaData时,不会包含ChunkServer相关的信息,在Master和ChunkServer启动时通过HeartBeat获取ChunkServer的相关信息。
3. 一致性模型
consistent:all clients will always see the same data, regardless of which replicas they read from。// 所有client读的数据是一致的
defined:consistent and clients will see what the mutation writes in its entirety。// 修改最终会生效
Q1:写文件时,如何做数据块合并,即最后一个chunk没有写满,如何追加到这最后一个chunk。
Q2:写数据时,需要同时修改Master中的Meta信息和ChunkServer中的chunk信息,如何同步?
Q3:并发写数据时,如何做同步,即需要等前面的写完以后才开始写后面的数据吗?如果不这样,中间的写失败了如何处理?
Q4:并发append为什么会导致duplicate?
Q5:并发write和append的区别是什么?
四、系统交互
4.1 系统交互类型
- 写数据。
- 读数据。
- 删除、移动和拷贝。
4.2 锁机制
对文件进行并发交互时,冲突会导致数据的不确定,因此需要加锁。锁分为读写锁,并且针对绝对路径加锁,规则如下:
路径:/dir1/dir2/dir3/dir_or_file
读操作:对目录dir1、dir2、dir3、dir_or_file加读锁(即这些路径只能被并发读)。
写操作:对目录dir1、dir2、dir3加读锁,dir_or_file加写锁(dir_or_file不能读也不能写)。
Q1:死锁的处理,操作1:将/dir1/dir2移动到/dir3/dir4目录下(dir2不能读写且需要读写dir4),操作2:将dir3/dir4移动到/dir1/dir2目录下(dir4不能读写且需要写dir2)。当2个操作并发时,可能会死锁。
4.3 删除、移动和拷贝
删除:采用延迟删除的方式,删除时增加标识,后台线程定时扫描要删除的文件。步骤如下:
1. 处理删除命令,标记Master中的Meta文件删除,通常在标记为删除3天后可正式删除。
2. 后台线程定时扫描Master中要删除的文件,删除相关的Meta信息。
3. ChunkServer通过心跳上报chunk信息时发现包含的chunk不对应任何有效文件,Master通知ChunkServer删除该chunk。
移动:在Master中修改Meta信息即可完成。
拷贝:采用COW机制,即使用时拷贝,首先拷贝一份Meta信息,并增加最后一个chunk的引用计数(如果一个文件有N个chunk,则前N-1个chunk无需拷贝,共享即可)。通过取消租约的方式停止向第N个chunk写数据,当有写操作时,先拷贝一份第N个chunk,取消引用计数,然后向拷贝出来的chunk写数据。
4.4 写数据
- 概述,client写数据时,先向Master具体写的chunk信息(Master通过租约lease的方式指定一个Primary chunk,其余的为Seondary chunk)。后续client向chunk发数据并向Primary发送写请求。
-
流程图
- 流程解析
step1、2:client发送请求,Master分配租约、返回chunk信息。
step3:client将要写的数据发送个chunk。
step4:client向Primary chunk发送写请求。
step5:Primary确定写数据的offset和顺序(即如果有多条数据,多条数据间的顺序),通知Secondary开始写数据。
step6:Secondary写数据成功,通知Primary。
step7:Primary通知client写数据成功。 - lease租约的管理
前提:当Chunk拥有租约时才会认为自己是Primary并处理写命令,通常租约为60S。
case1:写操作时租约自动到期。通过心跳来续租约,避免租约自动到期。
case2:Primary宕机。client重新向Master发送请求,Master重新分配租约。
case3:Primary网络波动,但新的Primary已产生,存在2个Primary。增加version字段,多个Primary时,比较version,同时Master存储一个version字段,version大于等于Master中的version时才有效,每次分配lease时,version自增。 - 特殊Case分析
case1:单client,数据不越界。无需特殊处理。
case2:单client,数据超出chunk容量。写失败,将chunk的空白区填充数据。
case3:单client,写失败,重试后成功。重试即可。
case4:单client,写失败,重试后仍失败(如某个chunk所在物理机宕机)。
case5:多client,数据不越界。无需特殊处理。
case6:多client,总数据超出chunk容量。未超出的部分写入chunk,超出部分失败重试。
case7:多client,总数据超出chunk容量,且在旧的chunk上写数据失败。 - 数据流的优化,从client将数据传给所有的chunk可以优化为client传给最近的chunk,由chunk链式传递给其它的剩下的chunk。
- 数据准确性的保证。在每个chunk末尾,记录当前chunk数据块的checksum。
4.5 读数据
根据offset定位到具体的chunk,读取数据,但是如上,存在空白区,读取时需特殊处理。
五、高可用的保证
5.1 chunk的复制
每个chunk都有多个副本,并可以配置副本的数量。
副本的分布:通常副本要在不同的机房,保证当整个机房出现问题时,数据不会丢失。
副本的拷贝:当副本配置数量增加或者部分副本永久失效时,需要拷贝副本(论文没有说明如何拷贝副本)。
5.2 chunk的过期检测
当Chunk服务器失效时,其中部分chunk数据更新不及时,因此上面的数据是无效的,在每个chunk中记录一个version(该version和lease的version相同),如果Master中其它同副本的chunk版本号更高,说明该chunk的数据未及时更新,需要删除该数据。
5.3 Master的复制
主从,RDB,AOF。。。
5.4 checksum保证数据完整
每个chunk块在末尾增加整个chunk的checksum,在读取时需根据记录的checksum校验该chunk块的数据的准确性。