试着读一下Google分布式三篇文章。MapReduce之前读过,感觉基础理论上理解起来比较简单,对于其在工程上的如容错处理等精髓待深入学习后再重读一遍。这里记录一下GFS论文里暂时学到的内容。
需求
节点无效是常态,因此需要注意节点的容错、监控以及恢复。
以存储大文件为主,一般为几百MB至几GB。
负载方面,有大内容连续读,小内容随机读,主要以追加式连续写的方式。
需要具有高效性、原子性。
高数据吞吐量比低延迟更加重要。
组成
由客户端、一个master、多个server组成,master负责维护元数据、与server进行心跳通信。
对于一个文件,首先由master切分成多个chunk,每个chunk大小为64MB,有一个64位的handle,并复制至多个replica中保存。
对于文件的传输,分为以下几步:
- 客户端与GFS通信,获取元数据
- 与具体server直接传输数据
- 客户端在一定时间内缓存元数据
元数据存于master的内存中,主要包括
- 文件、chunk的namespace
- 文件与chunk之间的映射关系
- 每个chunk的replica所在位置
数据一致性(存疑)
namespace由master管理于内存中,对其的修改需要通过master添加互斥锁,保证其原子性。
对于文件的修改,主要存在几个概念:
- 客户端对各replica读取不同,则不一致(inconsistent)
- 若全部相同,则一致(consistent)
- 所有客户端均能看到上次修改完整内容,则这部分文件是确定(defined)的
因此,在进行写操作后:
- 若本次只有一个写操作,则是确定的
- 若多个写操作并发且成功,则一致,但不确定
- 失败的写入导致不一致
对于修改数据,主要分为两种:
- 数据写入(write):覆盖操作,多个客户端并发会导致不确定
- 数据追加(record append):并发下也是原子的,确定
因此在GFS下更加倾向于追加操作,因为这样更强一致。
常见操作
namespace
- 不使用分层,是单纯的映射表
- 为了实现并发,每个文件、目录单独设置读写锁
- 为节省内存,锁在需要时创立,不需要时进行删除
- 锁获取操作按照相同的顺序,即不同层次按namespace的树层次来,同层次的按层内字典序,避免死锁
读取文件
- 客户端按照固定的chunk大小和文件的路径,计算所在chunk
- 客户端像master发请求,内容为文件名和chunk索引
- master响应chunk的handle,以及所有的replica的位置,客户端将其以kv对的形式存储
- 客户端向其中一个replica所在server发出请求,指定handle和读取范围
chunk lease
在某个chunk修改后,master将该chunk的lease交给某个replica,使其称为primary,对其他replica逐一修改。chunk lease有60秒的任期,未超时时可以向master申请延长时间,超时后master回收lease。
文件写入
- 客户端询问master,chunk的lease在哪
- master响应primary和所有replica
- 客户端向所有replica发送数据至缓冲区
- 在所有replica都收到数据后,客户端向primary发送写请求
- primary通知所有replica执行写操作
- 所有replica响应primary通知,写
- primary响应客户端,有错报错
文件追加
文件追加和文件写入相似
- 客户端将数据发送至所有replica缓冲区后,向primary发送追加请求
- primary判断追加后是否会超过chunk上限,若是的话就先将当前chunk填充满,并通知其他replica执行相同的操作,然后通知客户端在下一块chunk上重试
- 若空间足够,则primary通知所有replica追加位置的偏移量
- 操作完成后,primary响应客户端
文件快照(存疑)
- master回收这些chunk的lease,在接下来写入时创建新的chunk
- lease撤回或失效后,master写入日志,复制chunk
- 为新的chunk生成新的handle
replica管理
目标:最大化数据可用;最大化网络带宽利用率
来源:创建chunk;为chunk备份;replica均衡
删除文件
- namespace中不删除,而是重命名为一隐藏名,且带有时间戳
- master按时扫描namespace,若发现超出设定天数的隐藏内容,则进行移除
- namespace中移除后,对应chunk引用计数减一。master扫描所有chunk发现引用为0的chunk时,在内存中移除有关元数据,并通知replica自行移除