GFS 论文笔记

疑点已标红,基本都是多客户端并发追加相关。

设计假设:
1 系统构建在成百上千台机器上,节点故障是常态
2 系统需要存储很多大文件,大概百万千万级别,每个文件大约100MB或者更大。GB级别的文件很常见,应该被有效管理,需要支持小文件,但是不必做优化。
3 workload主要由两种读操作组成,
 1.小量数据随机读,每次从文件的某个偏移开始大概读几KB
 2.单次读操作 读上百KB甚至1MB或更多,来自同一个客户端的连续读通常读文件的某一个连续的区域。对性能敏感的应用程序通常batch它们的小量随机读操作,对读操作进行排序,offset由小到大

4 workload还有两种写操作组成:
    1.追加写,大小和读差不多,上百KB甚至1MB或更多。写成功后,文件几乎不会被修改。
 2.小量数据随机写,支持,不必做优化。

5 系统必须实现 多个客户端同时追加一个文件的well-defined 语义
6 利用高带宽比低延时更重要,大多数应用对延时不是很在意。

接口:
GFS提供了类似文件系统的接口。还支持record append和snapshot
   record append:多客户端追加同一个文件,保证每个客户端的原子性,参看后面Atomic Record Append
   snapshot:以很低的开销创建一个文件或者目录树的拷贝

架构:

 

每个文件被切分为固定大小的chunk,每个chunk创建的时候master会给它分配一个全局唯一的64bit的chunk handle
chunkserver存储chunk在本地磁盘,(chunk handle, byte range)决定读写位置
为了可靠性,chunk被复制多份存储在不同的chunkserver上,默认3份,用户也可以对文件系统名字空间的不同地方设置不同的复制份数

   Master职责:
   1 管理所有的文件系统元数据,包括namespace,访问权限信息,文件到chunk的映射关系和chunk的位置信息
   2 管理系统范围的活动,比如chunk lease的管理,无用chunk的回收,chunk的迁移。master周期性的和每个chunkserver发心跳消息以给chunkserver指令并收集chunkserver的状态

   应用程序利用GFS客户端与master,chunkserver交互,和master交互元数据,数据的读写直接和chunkserver交互。
   GFS客户端和chunkserver都不会缓冲文件内容:
       原因1. 客户端读的数据范围跨度太大,cache hit命中率低,无意义
    原因2,chunkserver上的chunk的缓存,Linux的page cache会接管
   但是GFS客户端cache元数据
单master结构:
 客户端问master应该与哪个chunkserver交互,然后与chunkserver直接通信。客户端缓存元数据一段时间。
 
一次简单的读操作的流程:
 1 客户端根据chunk size的大小,翻译(filename,byte offset)-> (filename,chunk index),发送(filename,chunk index)给master
 2 master 返回(chunk handle,replicas[所有]的位置),客户端cache这个信息,以(filename,chunk index)作为key
 3 client发(chunk handle,byte range)给其中一个replica,通常是最近的一个。以后对同一个块的读写不要经过master直到缓存的元数据过期或者文件被重新打开。
 实际上,客户端每次会问master要多个chunk的信息。
 
Chunk Size
chunk size 默认选择64MB,比普通文件系统的block size(通常4KB)大很多,每个chunk以普通的linux file存储,只有增加数据的时候,文件才会变大。避免了空间的浪费。

大的chunk size有很多好处:
1 减少了客户端与master的交互次数
2 减少网络开销,客户端的许多操作更有可能只会操作一个chunk,这样只需要与chunkserver维护一个长TCP连接
3 减少了master维护的元数据,节约内存

大chunk size的坏处:

1 小文件可能只会由一个chunk(即使磁盘空间采用lazy allocation)组成。当多个客户端同时读写同一个文件时,这个会成为瓶颈,实践中,由于应用的特点,这种情况很少

Metadata

In Memory Data Structures

master存储三种元数据:
    1 文件和chunk的namespace
 2 文件和chunk的映射
 3 chunk的位置
 
所有这些数据都存储在内存中。
前两种元数据也会被持久化,通过Log mutation 到本地磁盘的operation log并且被复制到远程机器。通过log使得更新master的状态更简单可靠。
第三种信息不会被持久化,是因为master启动的时候会问chunkserver要chunk的位置信息,当新的chunkserver加入到集群中也是一样

master会定期扫描master的状态来实现chunk的垃圾回收,chunk的重新复制(chunkserver 故障),chunk迁移(负载均衡,chunkserver磁盘空间均衡和load的均衡)
master为了维护每个64MB的chunk,需要小于64Byte的元数据,通过使用前缀压缩,文件namespace元数据每个文件通常也只需要小于64Byte。

chunk locations
master通过HearBeat 消息监控chunkserver的状态

Operation Log

operation log 保存了关键元数据改变的历史记录。它很重要,不仅仅是由于它是唯一的元数据的持久记录,也因为它记录了逻辑上的多个客户端的并发写操作的顺序。
文件,chunk,它们的版本信息都被这个逻辑上的时间所唯一标识

必须可靠的存储opertion log。元数据的改变持久化后 这个改变才能对客户端可见。所以,只有operation log持久化在本地磁盘并且也持久化到远程机器上的时候,
才能返回给客户端。master会batch 一些log record,然后一起提交,所谓的group commit
master通过回放operation log来恢复master的状态。但是当log文件比较大的时候,回放可能比较慢,针对这个问题,当master的operation log达到某个size的时候,
master会checkpoint当前的master的状态到磁盘上,以后每次恢复的时候,先载入checkpoint文件,然后再回放日志即可。

checkpoint 文件类似于B-tree,能够直接被map到内存进行查找,不需要额外的解析工作。

由于做checkpoint需要一段时间,为了不影响后续的mutation operation,master会切换到一个新的log文件,随后的log都记录在这个文件中,然后后台会有一个
线程对master的状态进行checkpoint。checkpoint文件会被写到远程机器上。、

Consistency Model

GFS 保证:
 1 文件namespace的改变(比如create file)是原子的,这个操作只被master做,lock保证了并发时的正确性和原子性,operation log决定了这些并发mutation的全局顺序

一个file region是consistent的 当所有的客户端看到的数据都是一样的,而不管客户端读哪个replica
在data mutation之后,一个file region是defined的 if它是consistent && 客户端能够看到实际上修改的结果(defined 是这个意思?)。在单客户端修改的情况下,consistent和defined是一样的,只要是consistent的就是defined的

并发的成功的mutation会让file region是consistent的,但是不是defined,因为每个客户端append的数据可能会被别的客户端给打断,发生交错的情况。
失败的mutation是导致file region 是inconsistent的:不同的客户端可能会看到不同的数据。

应用程序怎么区分 defined region和 undefined region(参看Implications for Application)

data mutation有两种:
1 write:写数据到应用程序指定的file offset
2 record append:将数据(record)至少一次原子性的append到文件中,offset由GFS选择(对应的,regular append是将data append到某个offset[应用程序认为这个offset是文件的尾部])。 这个offset会返回给客户端,标识了一个defined region,这个defined region中包含了这个record.
后面还有这么一句话:
In addition, GFS may insert padding or record duplicates in between. They occupy regions considered to be inconsistent and are typically dwarfed by the mount of user data

在一系列的成功mutation后,被修改的file region保证是defined,包含了最后一次mutation的内容。GFS通过两种手段来达到:
 1 在所有的replicas上的mutation顺序都是一样的
 2 使用chunk version来检测过期(stale,当chunk所在chunkserver宕机的时候,chunk会错失mutation)的chunk。过期的chunk以后不会involve进来,系统会进行回收

    因为客户端会cache元数据,所以客户端可能会access 过期的chunk,由于大多数文件都是append-only的,所以通常对stale的chunk会返回premature end of chunk 而不是过期的数据。下次客户端重试的时候就会联系master.

系统长期运行,一旦某个replica corrupted,数据会尽快从valid的replica恢复。如果一个chunk的所有的replicas都坏了,那么对应的chunk是unavailable的,会直接返回应用程序错误,而不是corrupted的数据。

Implications for Application(应用程序处理inconsistent region的方法,这节不太清楚)

GFS应用程序通过一些技术来适应GFS宽松的一致性模型:append不overwrite,checkpointing,写 self-validating record 和 self-identifying
在google,只append,不overwrite
在google的典型场景:
 1 一个客户端写一个文件,写完所有数据后,然后改文件名
 2 一个客户端写文件,周期性的checkpoint 成功写了多少数据,应用级别的checksum等数据到GFS中,(这就是所谓的checkpointing 和 写self-validating record吧)
 读client会verify,并且处理最后一个checkpoint(checkpoint record在defined region中)之前的数据。这个方法在google用的比较好。这种checkpoint方法可以让写客户端
 重启后增量写(因为checkpoint record中有已经写了多少数据)
 
 3 多客户端同时追加文件,append-at-lease-once语义保证了客户端的输出的完整性。客户端读时偶尔需要处理padding和重复record的情况。如下所述:
 
  客户端写record的时候,在record中包含额外的信息如checksum,读的时候通过这个checksum来丢弃padding和record fragment ,如果不能容忍重复record,可以在record中保存一个unique id来过滤重复的record
 
系统交互

a mutation是对chunk的内容或者元数据进行改变的操作,例如append。系统使用lease来使得mutation在所有的replicas上的更新顺序一致。
master给其中一个replica lease,这个replica 成为primary。primary选择一个mutation的顺序,然后其它的replica就遵照这个顺序。

lease机制被设计成最小master的管理开销。lease起始有60s的时间,当chunk被mutate的时候,chunkserver可以request master 延长lease,通常接收到的延长lease
的消息是放在HeartMessage中的。master可以回收lease(比如当一个文件正在被重命名的时候,不允许写)。

write流程:

 

 

 

1 客户端问master,谁hold了某个chunk的lease,所有的replicas的位置.如果当前没有replica有lease,那么master给lease给其中一个replica
2 master把primary replica的id和所有的replicas的位置给client,客户端缓存它。当缓存过期或者file reopened或者primary unreachable或者primary 回复说自己不拥有lease的时候才会再次联系master
3 客户端push数据给所有的replicas。这些数据会缓存到chunkserver的LRU buffer中,知道数据被使用或者被踢出。分离控制流和数据流,可以利用网络拓扑结构提高push数据的性能,后面详述
4 收到所有的replicas的确认后,客户端发起写请求,写请求会标识出刚才它push的数据。由于多个客户端可能同时会write一个chunk,primary给
接收到的每一个mutation设置一个序列号,决定了mutation的顺序。然后primary按照序列号的顺序应用mutation(也要写operational log吧?)
5 primary把写请求转发给其它的replicas,这些replicas应用mutation
6 replicas 应用完mutation后回复primary
7 primary 回复给客户端,在replica上发生的错误会报告给客户端,错误情况是指(primary 成功,但是某些replica不成功)。如果primary失败,那么就不会转发写请求给其它的
replicas。客户端写请求失败了,意味着被修改的区域成为inconsistent了。客户端会处理这种情况,通过重试mutation。重复第3~7步。

如果客户端写操作的数据很大或者跨了chunk的边界,那么写操作会被break成多个写操作,都遵循上面的流程。这样带来的问题是,写操作可能会被interleaved or overwriten
被其他的客户端。那么file region就会包含来自不同客户端的fragment,这会导致file region是consistent的,但是不是defined(怎么解决)

Data Flow

每台机器push data给在网络拓扑结构中最近的还没收到data的机器。
机器间的distance是用ip地址来衡量的
比如有四台机器S1,S2,S3,S4
S1离客户端最近,客户端push data 给S1,然后S1 发现S3离它更近,S1就push给S3,依次类推
为了减少延迟,采用TCP传输数据。并且当某台机器接收到了部分数据,就开始转发。发数据不影响受数据的效率。
发送B字节的数据给R个replicas,B/T + R*L   T是网络的吞吐量,L是在两个机器之间传输数据的延时,R*L是延迟
在google,T=100Mbps L远低于1ms,所以1MB数据80ms能传完

Atomic Record Append
它仅仅保证数据至少被原子性的写一次(写入一个顺序的byte流),有可能多次,出现duplicate record

Record Append的流程和write的流程差不多,不同的是,Record Append的时候,到数据被推到文件的最后的一个chunk的所有的replicas后,primary replica会检查新来的数据
会不会超过replica的boundary,如果会,就pad(这就是前面提到的padding)当前这个replica到64MB,也告诉其它的replicas这么做,然后返回给客户端,让客户端在下一个chunk上重试。如果没有超过,一切照常。

如果record append在某个replica上失败了,客户端会重试操作。可能会造成一些结果:同一个chunk的不同的replicas会包含不一样的数据,可能会包含重复的record,或者也可能
包含record的一部分,GFS不保证不同的replicas的数据每个bit都是一样的。它仅仅保证数据至少被原子性的写一次(写入一个顺序的byte流)。这个属性保证了:如果record append操作成功了,那么数据被写到了同一个chunk的所有的replicas的相同的offset。这个region是defined(后面有一句原文,感觉不对),whereas intervening regions are inconsistent(hence undefined).Our applications can deal with inconsistent regions as we discussed in Section2.7.2(Implications for Application)
这里有点不明白,这里的inconsistent region应该是呼应与前面的 padding or duplicate record 那一段吧?

Snapshot

使用copy on write实现snapshot
master收到snapshot的请求时,回收snapshot涉及的文件相关的chunk的lease,导致对这些文件的操作以后都需要联系master(primary replica回复client它已经不再hold lease)。

回收完这些lease后,master记下snapshot操作的log,然后执行snapshot操作,大概就是chunk的引用计数+1,复制源文件或者目录树相关的元数据,这份元数据也
指向相同的chunk
snapshot操作过后,当客户端想写chunk C的时候,它会问master哪个replica持有chunk C的lease,这个时候,master发现chunk C的ref大于1,随后master推迟回复客户端,
master pick一个新的chunk handle C', master要求拥有chunk C的机器本地copy出一个chunk出来,这个chunk就叫做C',然后master给其中一个replica lease,最后回复
客户端,后续的操作都是一样。


到这,应用程序处理inconsistent region的过程不太明白,多客户端并发追加太复杂,实现更不用说了,HDFS也抛弃了这个特性。

 

master operation

master管理所有的元数据操作,管理系统范围内的chunk replicas:
1 chunk放置位置的决定
2 创建新的chunk及其各个replicas
3 协调系统范围内的活动来保持chunk副本的个数,负载均衡,回收没有使用的chunk


4.1 命名空间管理和锁

很多master operation需要花很长的时间,例如,snapshot operation的时候需要回收snapshot操作涉及的chunk的lease。
所以当在进行snapshot的时候我们不想delay其他的master operation。所以我们允许多个操作时active的,使用锁来保护
名字空间相应的region来确保适当的序列化。

GFS没有per-directory数据结构(列出目录下面所有的文件).也不支持文件和目录的alias,软硬链接。逻辑上来说,名字空间
是一个文件完整路径名到元数据的lookup表。使用前缀压缩,这个表可以有效的放入内存。名字空间树上的每个节点(要么是一个
绝对路径的文件名要么是一个绝对路径的目录名)都有一个相关的读写锁。

每个master operation在执行之前都需要获得很多的锁。例如,如果操作涉及到/d1/d2/.../dn/leaf ,它需要首先获得/d1,/d1/d2,
... /d1/d2/.../dn目录的读锁 和/d1/d2/.../dn/leaf的读锁或者写锁。这里,leaf可能是目录也可能是文件。

举个例子来说明这种lock机制如何阻止当/home/user正在被snapshot到/save/user的时候,/home/user/foo文件被创建。

snapshot操作需要获得/home和/save的读锁,/home/user和/save/user上的写锁,而文件创建需要获得/home和/home/user上面的读锁
和/home/user/foo上面的写锁.这两个操作会被序列化,因为这两个操作需要获得的锁在/home/user上面冲突。文件创建操作不需要
获得父目录的写锁因为没有"目录"或者inode-like 的数据结构需要在修改的时候被保护。名字上面的读锁已经足够用来保护父目录
被删除.

这种锁机制一个很好的特性就是它能允许同一个目录下面的并发mutation。例如,可以并发的在同一个目录下创建多个文件:每个
创建操作都只需要获得目录上面的读锁和文件名上面的写锁。目录名上的读锁能够防止目录被删除,重命名和snapshot。文件名上
的写锁可以序列化两次创建相同名字的文件。

因为名字空间有许多的节点,所以,读写锁对象是被lazily分配的,并且,一旦不使用就删除。锁以一个一致的顺序获取以避免
死锁:在名字空间树种,他们首先根据树的深度排序,然后在同一层中,根据字母序排序。

4.2 replica placement

chunk replica 放置策略主要有两个目的,最大化数据的可靠性和可用性,最大化网络带宽利用率

4.3 创建,重新复制,负载均衡

chunk replica会由于三种原因被创建:1 chunk的创建 2 重新复制 3 重新负载均衡

当master创建一个chunk的时候,它选择在哪里放置新的空的replica。主要考虑如下因素:
1 chunkserver上面磁盘的利用率,尽量将replica放到低磁盘利用率的机器上
2 限制chunkserver机器最近的创建的数目,为了防止单个chunkserver压力过大
3 尽可能将replicas散布在不同的机架

master会重新复制chunk当replica的副本数低于设置值时,有很多情况会需要重新复制:

1 chunkserver成为unavailable
2 它报告replica可能损坏
3 其中一个磁盘出现error不能用
4 系统指定副本的数量需要更多。

每个需要重新复制的chunk的优先级的指定基于以下几个因素:
1 chunk离指定的replication的目标有多远,大概就是需要复制3份,那么
现在只有一个replica的chunk的复制优先级比有两个replica的chunk的复制优先级更高

2 趋向于复制live file的chunk,而不是deleted file的chunk

3 为了最小化失败对应用程序的影响,我们提升可能会阻塞客户端过程的chunk的优先级。

master选择优先级最高的chunk,然后指示一个chunkserver从一个已经存在的合法的replica上复制一份,同样,replica
的放置规则也会考虑前面提到的放置因素。为了防止重新复制给性能带来影响,master会
限制每台chunkserver和集群上的活跃的clone操作的个数。另外,每台chunkserver还会
限制clone操作所使用的带宽。

最后,master周期性的进行重新负载均衡,通过检查replica的分布

 

 

 


 

posted @ 2012-01-05 17:34  吴镝  阅读(2248)  评论(1编辑  收藏  举报