设计
主备master结构
master之间通过一致性协议保持同步,每个master都存储所有信息。
master的数据是落地的,不一定非得全内存,通过LRU内存cache保证热点访问的快速响应。
文件分块
文件是否分块决定了能否支持大文件
文件名信息是否存储在master决定了是否支持小文件
折衷是对大文件才分块,小文件(<100G)不分块;创建文件夹时指定可否list,可list的必须存在目录树中,不可list的可以哈希到chunkserver,让chunkserver去维护meta信息。
文件meta信息
文件meta信息存储在master会导致master负载过高(内存,cpu) 我们选择对与不分块的文件, 将文件meta存储在chunkserver,如果用户要list,那仅需要访问master,但如果要获取文件大小等信息,那就必须得访问对应的chunkserver了,master仅存储namespace和每个文件在哪些chunkserver上。
写数据流
有两个选择:
1. client写一份,然后chunkserver通过复制链创建多副本,优势是client带宽占用低;
2. client一次写多份,优势是可以多些一份,并在写的过程中抛弃慢节点。
考虑将这个做成个选项,两种都支持。
好吧,我们先来开发一个单master,不支持分块,不支持小文件的fs吧。
实现
Namespace
存储我们选择了leveldb,可以简单的将整个目录结构平展开,并提供高效的文件创建、删除和list操作。
实际目录结构:
/home/dirx/
/filex
diry/
/filey
/tmp/
filez
过去的存储格式为:
00/
01/home
01/tmp
02/home/dirx
02/home/diry
02/tmp/filez
03/home/dirx/filex
03/home/diry/filey
这样做的主要目的是高效支持list操作。
现在的存储格式为:
1home -> 2
1tmp -> 3
2dirx -> 4
2diry -> 5
3filez -> 6
4filex -> 7
5filey -> 8
其中所有数字都是64位整数,目的是支持高效list操作的同时,支持高效的rename操作。
rename操作中,文件夹名字改变,但编号不变。
Open for write
在namespace中写入, 创建块, 分配datanode。
如果失败,在nameserver回滚。
数据发往datanode, 中间网络故障可以重连,但一旦close后不可追加写入。
nameserver向client发放lease,lease失效后,不可以继续写入。
sdk端写入流程
- Write
如果_chains_head为空,则同步调用AddBlock
向_write_buf中append数据,如果_write_buf满了,调用StartWrite(_write_buf) - StartWrite
将_write_buf放入_write_queue中,然后抛出BackgroundWrite事件,++_back_writing - BackgroundWrite
如果_write_queue非空 且 最小的sequence小于滑动窗口上限,异步调用WriteBlock - WriteChunkCallback
如果WriteBlock失败,++_back_writing, delay一个DelayWriteChunk
如果WriteBlock成功,更新滑动窗口
如果_write_queue为空,back_writing为0,则唤醒sync, 调度一个新的BackgroundWrite - DelayWriteChunk
异步调用WriteBlock,--_back_writing
Open for read
在从namespace中获取datanode信息,向随机datanode读取数据。
读取不被保护,如果读过程中文件被删除,后续读取失败