GFS是谷歌内部使用的一种分布式文件系统. 论文为: The Google File System
- Assumption and Motivation
- 架构
- master的功能
- chunkserver的任务
- 操作日志(Operation Log)和checkpoint
- chunk lease
- 文件读取
- Append
- 容错
- 快照(Snapshot)
Assumption and Motivation
- 部件错误很常见.
- 文件非常大, GB大小的文件很常见.
- 绝大多数的文件修改操作是append, 也支持随机写.
- 读操作主要是顺序读取大量数据, 随机读取少量数据.
- 为多个客户端同时对同一文件append提供高效的同步支持, 保证append的原子性.
- 稳定的网络比低延迟更加重要.
架构
一个master和很多chunkserver, 多个客户端可以并发访问, 见Figure 1. 显然存在单点故障问题.
一个文件被划分为多个固定大小的部分, 称为chunk, 每个chunk都有一个chunk handle唯一标识, 是 在创建该chunk时由master分配的(由于只有一个master, 可以保证其唯一性).
每个chunk会被复制多份(一般是3份), 分散到不同chunkserver, 以保证容错. 由master选择其中一 个chunk授权, 称为lease, 其余称为replica, 获得lease的chunk所在chunkserver控制了这些副 本间的同步.
chunk由64KB大小的块组成, 称为block, 每个block在内存中有32bit的校验码. master记录了文 件系统的所有状态, 文件名到对应chunk handle的映射, 所有chunk所在的chunkserver, 同一 chunk中的lease, 等等。有的信息保存在磁盘, 有的只保存在内存中, 加快了这些信息的返回速度, 但 是每次master重启时都需要询问chunkserver来获取这些信息(chunk所在服务器地址).
master定期和每个chunkserver联系, 以发送指令或获取信息. 客户端和master的交互只涉及控制信 息, 状态信息等, 而没有数据. 数据直接在chunserver和客户端间流动, 减小了master的负载。
客户端不缓存数据, 但是客户端会缓存部分状态信息. 系统或其他部件实现的缓存不在讨论范围内. 在系 统中, 很容易根据服务器的ip获取最佳路径.
master的功能
- 维护层次文件系统结构, 文件系统用前缀树来保存。
- 维护filename-chunk映射
- 记录每个chunk的位置
- 管理并发数据修改操作的顺序, 采用读写锁来实现同步。注意GFS没有目录的概念。
- lease管理
- 检测, 处理失效chunk
- 定期检查chunk是否有效, 无效则重复制以保证副本数量。
- 采用lazy deletion, 客户端请求删除chunk时, 并不立即删除chunk, 只是将其标记为无效, 定时 删除这些chunk。
- 复制chunk, 保证至少有3个副本, 或满足LB的需要, 当某个chunk成为瓶颈时, 会增加此chunk的副本数量。
- 副本位置迁移
- 根据访问, 迁移副本的位置, 以提高带宽。
- 为了平衡chunkserver的数据量, 迁移副本。
chunkserver的任务
- 保存chunk
- 同步对chunk的写操作
操作日志(Operation Log)和checkpoint
checkpoint记录了某个时刻master的所有状态, master可以通过一个checkpoint恢复到那个时刻的 状态.
操作日志记录了master的写操作.
先载入最近的checkpoint, 再重放此checkpoint之后的所有操作日志, 即可恢复master的状态. 因 此, 采用日志先写的原则, 并将日志保存到磁盘中. master在日志达到一定大小后就建立checkpoint.
chunk lease
- 由于每个chunk有多个副本, 因此在写chunk时, 需要考虑如何保证副本间的一致性. GFS使用了Lease 的概念, 即授予一个chunk lease, 获得lease的chunk所在的服务器协调副本的写, 称此副本为 primary. 这个想法最初是来自论文 <<Leases: An Efficient Fault-Tolerant Mechanism for Distributed File CacheConsistency.pdf>>. 详细的写入步骤在文件append中.
文件读取
下面是理想情况下的步骤:
- client根据文件偏移计算对应chunk号, 向master发送消息询问chunk地址, 消息包含: 文件名, chunk号.
- master返回:chunk handle, chunk所在服务器ip列表(因为有多个副本).
- client根据ip计算最近服务器, 发送消息请求数据。消息包含: chunk handle, 要读取的数据范围 (byte range).
- chunkserver返回数据.
之后一段时间client会缓存该chunk的地址, 再次读取chunk时不需要与master交互。
Append
下面是理想情况下的步骤:
- client询问要append的chunk的primary及其地址。
- master如果发现此chunk还没有授予lease, 就选择一个授予lease. 返回primary和其他副本 (secondary)的地址.
- client通过所有副本的ip地址, 计算出一条分发数据的路径, 然后推送数据. 中间收到数据的副本 所在服务器在收到部分数据后就立即向其他副本所在服务器转发. 而不是等到所有数据都受到后才转发. 收到的数据被还没有写入.
- 副本所在服务器收到数据后, 向client返回ack, 并包含一个id以标记此数据.
- client收到所有副本的ack之后, 向primary请求开始写入. 请求中必须包含此数据的id(副本返回的).
- primary收到写入请求, 尝试写入, 如果成功就为之赋予一个序号, 以此将所有写入序列化, 并将请求 转发到secondary. 如果失败则向client返回错误, return.
- secondary完成写入后, 回复primary.
- 当primary收到所有secondary的写入成功回复后, 即回复写入成功. 否则返回写入失败.
容错
当append时, primary写入失败, 如何处理?
此时primary chunk有错误数据, 而secondary对应区域没有数据, 关键就是保证副本间的一致性. GFS的处理简单粗暴, 放弃这个区域, 重新选择一个区域append.
那么又有问题了, 再之后顺序读取中, 如何识别区域是无效的呢?
好像文章里没有说, 不过实现起来应该很简单. 因为有检验码, 或者有其他标志.
如果primary成功, 某些secondary失败呢?
参照上面的方法处理.
在chunkserver向客户端或其他chunkserver返回数据时, 都会检查每个chunk的校验码, 防止错误数 据扩散.
master的状态会复制到多个机器上, 这些机器作为备份。操作日志会保证先写到这些备份机器上再在 master执行. master重启速度非常快, 并且从各个chunkserver获取chunk位置的速度也很快.
快照(Snapshot)
复制一个文件/目录, 采用copy-on-write策略. 因此每个文件有一个引用计数, 当修改引用计数超过1的 文件时, 需要将其复制一份才能修改.