4.1.2 关键问题
1.租约机制
GFS数据追加以记录为单位,每个记录的大小为几十KB到几MB不等,如果每次记录追加都需要请求Master,那么Master显然会成为系统的性能瓶颈,因此,GFS系统中通过租约(lease)机制将chunk写操作授权给ChunkServer。拥有租约授权的ChunkServe称为主ChunkServer,其他副本所在的ChunkServer称为备ChunkServer。租约授权针对单个chunk,在租约有效期内,对该chunk的写操作都由主ChunkServer负责,从而减轻Master的负载。一般来说,租约的有效期比较长,比如60秒,只要没有出现异常,主ChunkServer可以不断向Master请求延长租约的有效期直到整个chunk写满。
假设chunk A在GFS中保存了三个副本A1、A2、A3,其中,A1是主副本。如果副本A2所在ChunkServer下线后又重新上线,并且在A2下线的过程中,副本A1和A3有更新,那么A2需要被Master当成垃圾回收掉。GFS通过对每个chunk维护一个版本号来解决,每次给chunk进行租约授权或者主ChunkServer重新延长租约有效期时,Master会将chunk的版本号加1。A2下线的过程中,副本A1和A3有更新,说明主ChunkServer向Master重新申请租约并增加了A1和A3的版本号,等到A2重新上线后,Master能够发现A2的版本号太低,从而将A2标记为可删除的chunk,Master的垃圾回收任务会定时检查,并通知ChunkServer将A2回收掉。
2.一致性模型
GFS主要是为了追加(append)而不是改写(overwrite)而设计的。一方面是因为改写的需求比较少,或者可以通过追加来实现,比如可以只使用GFS的追加功能构建分布式表格系统Bigtable;另一方面是因为追加的一致性模型相比改写要更加简单有效。考虑chunk A的三个副本A1、A2、A3,有一个改写操作修改了A1、A2但没有修改A3,这样,落到副本A3的读操作可能读到不正确的数据;相应地,如果有一个追加操作往A1、A2上追加了一个记录,但是追加A3失败,那么即使读操作落到副本A3也只是读到过期而不是错误的数据。
我们只讨论追加的一致性。如果不发生异常,追加成功的记录在GFS的各个副本中是确定并且严格一致的;但是如果出现了异常,可能出现某些副本追加成功而某些副本没有成功的情况,失败的副本可能会出现一些可识别的填充(padding)记录。GFS客户端追加失败将重试,只要返回用户追加成功,说明在所有副本中都至少追加成功了一次。当然,可能出现记录在某些副本中被追加了多次,即重复记录;也可能出现一些可识别的填充记录,应用层需要能够处理这些问题。
另外,由于GFS支持多个客户端并发追加,多个客户端之间的顺序是无法保证的,同一个客户端连续追加成功的多个记录也可能被打断,比如客户端先后追加成功记录R1和R2,由于追加R1和R2这两条记录的过程不是原子的,中途可能被其他客户端打断,那么GFS的chunk中记录的R1和R2可能不连续,中间夹杂着其他客户端追加的数据。
GFS的这种一致性模型是追求性能导致的,这增加了应用程序开发的难度。对于MapReduce应用,由于其批处理特性,可以先将数据追加到一个临时文件,在临时文件中维护索引记录每个追加成功的记录的偏移,等到文件关闭时一次性将临时文件改名为最终文件。对于上层的Bigtable,有两种处理方式,后面将会介绍。
3.追加流程
追加流程是GFS系统中最为复杂的地方,而且,高效支持记录追加对基于GFS实现的分布式表格系统Bigtable是至关重要的。如图4-2所示,追加流程大致如下:
图 4-2 GFS追加流程
1)客户端向Master请求chunk每个副本所在的ChunkServer,其中主ChunkServer持有修改租约。如果没有ChunkServer持有租约,说明该chunk最近没有写操作,Master会发起一个任务,按照一定的策略将chunk的租约授权给其中一台ChunkServer。
2)Master返回客户端主副本和备副本所在的ChunkServer的位置信息,客户端将缓存这些信息供以后使用。如果不出现故障,客户端以后读写该chunk都不需要再次请求Master。
3)客户端将要追加的记录发送到每一个副本,每一个ChunkServer会在内部的LRU结构中缓存这些数据。GFS中采用数据流和控制流分离的方法,从而能够基于网络拓扑结构更好地调度数据流的传输。
4)当所有副本都确认收到了数据,客户端发起一个写请求控制命令给主副本。由于主副本可能收到多个客户端对同一个chunk的并发追加操作,主副本将确定这些操作的顺序并写入本地。
5)主副本把写请求提交给所有的备副本。每一个备副本会根据主副本确定的顺序执行写操作。
6)备副本成功完成后应答主副本。
7)主副本应答客户端,如果有副本发生错误,将出现主副本写成功但是某些备副本不成功的情况,客户端将重试。
GFS追加流程有两个特色:流水线及分离数据流与控制流。流水线操作用来减少延时。当一个ChunkServer接收到一些数据,它就立即开始转发。由于采用全双工网络,立即发送数据并不会降低接收数据的速率。抛开网络阻塞,传输B个字节到R个副本的理想时间是B/T+RL,其中T是网络吞吐量,L是节点之间的延时。假设采用千兆网络,L通常小于1ms,传输1MB数据到多个副本的时间小于80ms。分离数据流与控制流主要是为了优化数据传输,每一台机器都是把数据发送给网络拓扑图上“最近”的尚未收到数据的数据。举个例子,假设有三台ChunkServer:S1、S2和S3,S1与S3在同一个机架上,S2在另外一个机架上,客户端部署在机器S1上。如果数据先从S1转发到S2,再从S2转发到S3,需要经历两次跨机架数据传输;相对地,按照GFS中的策略,数据先发送到S1,接着从S1转发到S3,最后转发到S2,只需要一次跨机架数据传输。
分离数据流与控制流的前提是每次追加的数据都比较大,比如MapReduce批处理系统,而且这种分离增加了追加流程的复杂度。如果采用传统的主备复制方法,追加流程会在一定程度上得到简化,如图4-3所示:
图 4-3 GFS追加流程(数据流与控制流合并)
1)同图4-2 GFS追加流程:客户端向Master请求chunk每个副本所在的ChunkServer。
2)同图4-2 GFS追加流程:Master返回客户端主副本和备副本所在ChunkServer的位置信息。
3)Client将待追加数据发送到主副本,主副本可能收到多个客户端的并发追加请求,需要确定操作顺序,并写入本地。
4)主副本将数据通过流水线的方式转发给所有的备副本。
5)每个备副本收到待追加的记录数据后写入本地,所有副本都在本地写成功并且收到后一个副本的应答消息时向前一个副本回应,比如图4-3中备副本A需要等待备副本B应答成功且本地写成功后才可以应答主副本。
6)主副本应答客户端。如果客户端在超时时间之内没有收到主副本的应答,说明发生了错误,需要重试。
当然,实际的追加流程远远没有这么简单。追加的过程中可能出现主副本租约过期而失去chunk修改操作的授权,以及主副本或者备副本所在的ChunkServer出现故障,等等。由于篇幅有限,追加流程的异常处理留作读者思考。
4.容错机制
(1)Master容错
Master容错与传统方法类似,通过操作日志加checkpoint的方式进行,并且有一台称为"Shadow Master"的实时热备。
Master上保存了三种元数据信息:
●命名空间(Name Space),也就是整个文件系统的目录结构以及chunk基本信息;
●文件到chunk之间的映射;
●chunk副本的位置信息,每个chunk通常有三个副本。
GFS Master的修改操作总是先记录操作日志,然后修改内存。当Master发生故障重启时,可以通过磁盘中的操作日志恢复内存数据结构。另外,为了减少Master宕机恢复时间,Master会定期将内存中的数据以checkpoint文件的形式转储到磁盘中,从而减少回放的日志量。为了进一步提高Master的可靠性和可用性,GFS中还会执行实时热备,所有的元数据修改操作都必须保证发送到实时热备才算成功。远程的实时热备将实时接收Master发送的操作日志并在内存中回放这些元数据操作。如果Master宕机,还可以秒级切换到实时备机继续提供服务。为了保证同一时刻只有一台Master,GFS依赖Google内部的Chubby服务进行选主操作。
Master需要持久化前两种元数据,即命名空间及文件到chunk之间的映射;对于第三种元数据,即chunk副本的位置信息,Master可以选择不进行持久化,这是因为ChunkServer维护了这些信息,即使Master发生故障,也可以在重启时通过ChunkServer汇报来获取。
(2)ChunkServer容错
GFS采用复制多个副本的方式实现ChunkServer的容错,每个chunk有多个存储副本,分别存储在不同的ChunkServer上。对于每个chunk,必须将所有的副本全部写入成功,才视为成功写入。如果相关的副本出现丢失或不可恢复的情况,Master自动将副本复制到其他ChunkServer,从而确保副本保持一定的个数。
另外,ChunkServer会对存储的数据维持校验和。GFS以64MB为chunk大小来划分文件,每个chunk又以Block为单位进行划分,Block大小为64KB,每个Block对应一个32位的校验和。当读取一个chunk副本时,ChunkServer会将读取的数据和校验和进行比较,如果不匹配,就会返回错误,客户端将选择其他ChunkServer上的副本。