跳转到内容
← 返回系统剖析
系统剖析当代约 16 分钟阅读

GFS:单主节点、大块与可忍受的不一致

GFS: Single Master, Large Chunks, and Consistency MapReduce Could Live With

GFS分布式文件系统块复制记录追加松弛一致性

2003 年 10 月,SOSP 在纽约州博尔顿兰丁开会。桑贾伊·格马瓦特(Sanjay Ghemawat)、Howard Gobioff 和 Shun-Tak Leung 交出的不是又一套 POSIX 网络盘,而是 Google 已经跑在生产里的存储:单主节点、一群 chunkserver、约 64 MB 的大块、默认三副本,以及一种以追加为中心、故意放松的一致性。当时最大的集群已经跨过一千台机器、数千块磁盘、数百 TB。

这篇论文要回答的问题很具体。爬虫、索引器和日志会写出巨大的、几乎只追加的文件;机器每天都在坏。他们要的不是桌面文件系统的语义,而是一种应用程序愿意配合的存储形状。一年后的 MapReduce 正是在这层形状上长出来的。三年后的 Bigtable 把稀疏有序表铺在这些文件上,那是另一张映射,不是把块再发明一次。后来 GFS 用 Chubby 锁指定主节点,那是 2006 年锁服务对象,不是 2003 年这篇的块布局。对象是 2003 年这套设计,不是 2004 年那套编程模型,也不是后来开源世界把同一思路教科书化之后的 Hadoop,更不是 Chord、Kademlia 或 Pastry 那种对等查找。

破除误解

  1. 误解**误解一:单主节点等于单点故障,规模一大就会先把自己噎死。

    实情**

主节点确实是元数据的唯一决策者。它管命名空间、文件到块的映射、租约和再复制。它不管数据面。客户端问一次块在哪,就把句柄和副本位置缓存起来,随后的读写直连 chunkserver。论文里两套生产集群的主节点元数据分别只有 48 MB 和 60 MB,大约每个文件 100 字节;主节点操作大约每秒两三百到五百次,对当时的负载构不成瓶颈。主节点会挂,所以操作日志要在本地和远程都刷盘之后才对外可见,还有只读的影子主节点。单主节点换来的是全局放置决策,付出的是故障窗口,不是“把所有字节都从一台机器过一遍”。

误解二:64 MB 的块只是把磁盘块调大,浪费空间,也更热。

传统文件系统的块是 4 KB 量级,为的是随机小写。GFS 的工作负载几乎相反:文件常常是多 GB,读写以顺序流为主。大块让客户端少问主节点,也能在同一条 TCP 连接上连续打许多操作,主节点内存里的映射项随之变少。内部碎片用延迟分配压住:副本在 chunkserver 上就是普通 Linux 文件,用多少长多少。热块确实出现过——早期批处理把可执行文件写成单块,再让几百台机器同时去拉,那几台 chunkserver 当场被打满。他们的修补是提高复制因子、错开启动,而不是把块改回 4 KB。64 MB 是对访问形状的赌注,不是对“块越大越好”的迷信。

误解三:放松一致性等于没有一致性,GFS 可以当普通磁盘用。

GFS 不提供 POSIX。它把文件区域分成三种可检查的状态:定义的(defined,副本一致且读者看见完整的那次变异)、一致但未定义(各副本相同,却可能是并发写搅在一起的碎片)、不一致(不同读者可能看见不同字节)。串行成功的写给出定义区域;并发成功的写给出一致但未定义;失败的写留下不一致。记录追加保证至少一次原子写入,中间可能夹填充和重复。应用程序用校验和、自描述记录和幂等重试去消化这些缝。MapReduce 能在这上面活,正因为它把输入切成独立记录、把输出写成新文件再原子改名,并不需要在同一偏移上做可序列化的覆盖写。把 GFS 当成家用磁盘,会在第一次并发追加时得到“正确但难看”的文件。

单主节点与数据面分离

一个 GFS 集群由一台主节点和许多 chunkserver 组成,客户端以用户态库链进进程。机器是普通 Linux。文件切成固定大小的块,每块有一个主节点在创建时分配的、不可变的 64 位句柄。chunkserver 按句柄加字节范围在本地磁盘上读写。默认三副本,命名空间的不同区域可以指定不同复制因子。

主节点持有三类元数据:命名空间与权限、文件到块的映射、每块当前的副本位置。前两类写入操作日志并复制到远程机器;位置信息不持久化。启动时主节点向各 chunkserver 询问“你有哪些块”,之后靠心跳维持。这样设计是因为位置是 chunkserver 的事实,不是主节点可以单方面宣布的事实。机器挂了再回来,以它自己报告的为准,避免主节点磁盘上的位置表和现实打架。

客户端从不把文件数据送给主节点。读路径是:用固定块大小把偏移换成块下标,问主节点要句柄和副本列表,缓存后向最近或最空的副本读。写路径更绕,但数据面仍然不经过主节点。这是单主节点能撑住的前提。把它理解成“所有流量的中心交换机”,就把控制和载荷叠成了一个词。分布式系统 里最贵的往往不是算法,而是谁必须出现在热路径上。GFS 把主节点请出热路径,只把租约和命名空间留在它手里。

副本放在哪:机器不够,还要跨机架

放置策略同时要可靠性和带宽。只把三副本散在三台机器上,挡得住单盘和单机,挡不住机架交换机或一条电源回路。GFS 把同一块的副本跨机架放置。整架掉线时,至少还有副本在别的架子上可读;读流量也可以吃到多条机架上联。写必须穿过多个机架,他们接受这笔税。

创建新块时,主节点还看三件事:磁盘用量低于平均的机器优先;限制单机上“最近刚创建”的数量,因为创建几乎预告马上会有追加写;以及跨机架。再复制(re-replication)在副本数掉到目标以下时启动。主节点按优先级挑块,让某台 chunkserver 从仍健康的副本直接克隆。为了不让修复流量压死客户流量,集群和单机都有并发克隆上限,单次克隆还限速。论文里一次实验杀掉集群 B 上一台持有约 15,000 块、600 GB 数据的 chunkserver,默认最多 91 路并发克隆、每路至多 6.25 MB/s,23.2 分钟内补齐,等效约 440 MB/s。再杀两台、把 266 块打到只剩一份时,这些块在两分钟内回到至少两副本。修复速度是调度出来的,不是“三副本”四个字自动给的。

这和后来 Dynamo 的环状偏好列表不是同一类对象。Dynamo 用一致性哈希让协调者在无中心目录时仍能猜出 N 个位置;GFS 用一个记得全局的主节点做显式放置。中心换来的是跨机架和再平衡可以按磁盘、按机架、按近期创建量一起算。没有中心,这些约束要改写成局部启发式。两种存储都复制,复制的决策权放在不同的人手里。

变异:租约、流水线,以及以追加为中心的突变

一次变异(mutation)是改块内容或元数据的操作,必须在所有副本上发生。主节点把某块的租约授给其中一个副本,称为主副本(primary)。租约初始 60 秒,块仍在被写时可通过心跳无限续约。主副本为所有变异编号,其他副本按同一顺序应用。全局顺序于是分成两层:主节点决定谁拿租约,租约期内由主副本的序号决定。主节点与主副本失联,等到租约过期再把租约交给别人,不会出现两个主副本同时编号。

数据流和控制流被拆开。客户端把数据沿一条按拓扑选好的 chunkserver 链流水线推送,每台机器的出方向带宽用来传给下一个,而不是扇出成树。所有副本确认收到缓冲后,客户端才向主副本发写请求。主副本编号、本地应用、转发给二级副本,等齐了再回客户端。任一层失败,这次写算失败,区域可能不一致,客户端重试。大写或跨块边界会被库拆成多次写;若与别人的写交错,各副本仍相同,但内容可能是碎片——一致但未定义。

真正让这套存储长成搜索管道的,是记录追加(record append)。客户端只给数据,偏移由 GFS 选。成功时,记录作为连续字节至少出现一次,偏移返回给客户端。记录不能大于块大小的四分之一,以免填充和边界情况把块切碎。若追加会撑破 64 MB,主副本把当前块填满,让客户端到下一块重试。失败再试,副本之间不必逐字节相同,只保证某次成功的记录在某一共同偏移上原子落地。填充和重复落在不一致区域。写的人在记录里带校验和与唯一标识,读的人丢弃碎片、必要时去重。爬虫日志、多路归并、生产者—消费者队列,靠的就是这种“至少一次、位置我来定”的语义。信息检索 要建的倒排表和网页库,形状正是不断追加的大文件,而不是在文件中间打补丁。

快照用写时复制。主节点收回相关租约、记下操作,然后只复制元数据,让新文件和旧文件先指向同一批块。第一次再写某块时,发现引用计数大于一,就在原 chunkserver 本地拷出新句柄——当时磁盘大约比 100 Mbps 以太网快三倍,所以拷贝不过网。命名空间上的读写锁让快照和创建可以并发交错,而不把主节点做成一把全局大锁。

MapReduce 能忍受的那种松弛

把一致性模型从接口上读一遍,才能看清应用程序被要求做什么。定义区域里,读者无论打到哪个副本,都看见那次变异的全部字节。一致但未定义意味着副本对齐了,语义却可能是两个人的写搅在一起。不一致意味着副本已经分叉。GFS 用同一顺序加版本号,保证成功的串行变异进入定义状态,并把错过变异的陈旧副本从位置应答里拿掉,稍后当垃圾收回。客户端缓存可能短暂指向陈旧副本;窗口受缓存超时和下一次 open 限制。因为文件多半只追加,陈旧副本常常表现为块过早结束,而不是吐出一份过期的中间覆盖。读者重试并再问主节点,就会拿到新位置。

这套松弛不是懒,是与故障模型的交换。分布式系统 里组件故障是常态:磁盘、内存、网线、电源、操作系统和人为误操作,论文把它们写成同一张清单。校验和按 64 KB 分块计算,读时校验;空闲时扫描很少被读的块,免得一份坏掉却从不被访问的副本一直占着“我还是三副本”的名额。所有副本在主节点来得及反应之前一起丢掉,块才不可恢复,时间尺度通常是分钟。即便如此,应用收到的是明确错误,不是静默烂数据。

MapReduce 的 map 只看见一条输入,reduce 只看见一个键。框架把失败的任务换台机器重跑,输出写到临时文件再改名。它不需要 GFS 在同一偏移上提供可串行化的覆盖写,它需要追加带宽、三副本在机器死亡后仍可读、以及记录边界清晰到可以跳过填充。GFS 的一致性账单开给了应用程序;MapReduce 把账单收进了库和命名约定。两者是同一家公司里成对的设计,对象仍然不同:一个管字节放哪、一次追加承诺什么;一个管计算怎么切、失败怎么重来。把 GFS 写成 大数据系统 总览里的一句话前身,会把这张账单从句子里删掉。

Dynamo 走了另一条放松。它默认永远可写,用向量时钟把并发版本留下来给业务合并。GFS 默认追加至少一次,用填充和重复换原子记录,用主副本的序号在一块之内排好顺序。购物车不能接受写失败;索引流水线不能接受“同一条记录的字节被撕开写到文件中间”。两种放松买的可用性不同,不能互换。后来的 HDFS 继承了大块、单名节点和三副本的外形,教材常从那里倒着讲 GFS。要看的仍是 2003 年那组接口:记录追加、快照、非 POSIX,以及主节点不持久化块位置。外形相似,并不自动带上同一张一致性表。

生产数字把“可忍受”钉死在负载上。集群 A 给一百多名工程师做研发,342 台 chunkserver,可用 72 TB、已用 55 TB,约 73.5 万文件、99.2 万块。集群 B 做生产数据处理,227 台 chunkserver,可用 180 TB、已用 155 TB。三副本意味着文件数据大约是已用空间的三分之一。测量那一周,A 的持续读大约 580 MB/s,接近其网络配置能支持的 750 MB/s;B 的突发写大约 100 MB/s,网络上因三副本变成约 300 MB/s。主节点没有成为这两套负载的瓶颈。更早的版本里,主节点曾在含数十万文件的目录上做线性扫描;改成可二分查找的命名空间之后,才能支撑每秒数千次文件访问。单主节点能用,是因为块够大、元数据够小、扫描够快,再加上应用几乎不靠它传字节。

跨域连接

  • 概率论:默认三副本把“块丢失”写成近乎独立故障的乘积。机架共享交换机和电源,独立性在那一层已经破裂,所以放置必须跨机架,而不是在同一架子上再放两份。克隆限速把修复时间从“越快越好”改成一条受控的分布:23.2 分钟补 600 GB,是在客户流量这个约束下截断了尾部。相关故障会让“三选一还活着”的组合计数立刻失效,论文用实验而不是用独立伯努利假设来报恢复时间。
  • 可靠性工程:GFS 把故障写成运行条件,而不是异常。校验和发现损坏,副本提供另一份,再复制把份数拉回目标,三层对应检测、冗余、修复。全部副本在反应窗口内一起消失时,系统选择不可用而不是假装读成功。这是失效分析里的诚实接口:错误必须可被应用看见。商品磁盘的失效率被集群规模放大之后,监控、心跳和后台扫描不再是运维附录,而是存储语义的一部分。
  • 公地治理:单主节点是全集群共享的协调公地。谁都可以问块在哪,但没有人被允许把数据面倒进这台机器。克隆并发与每路带宽上限,是给修复作业划的配额,免得补副本的人把读者的交换机吃光。批处理同时去拉同一个单块可执行文件,是公地被瞬时挤兑的经典形态;提高复制因子和错开启动,是使用规则,不是把块改小。治理盯的是主节点操作队列和克隆占用的机架上联,不是平均磁盘用量好看不好看。
  • 信息不对称:主节点不持久化块位置,等于承认“谁有数据”以 chunkserver 的自我报告为准。客户端缓存的位置表是一份会过期的私有知识,陈旧窗口里读到的可能是过早结束的块。记录追加把“写到文件哪个偏移”从客户端的私有猜测收归系统,避免一群生产者各自以为自己看见了文件尾。校验和则是读者用来检验对方有没有说真话的短证据。存储接口在这里处理的是局部知识,而不只是字节数组。

参考文献

  • Ghemawat, S., Gobioff, H. & Leung, S.-T. The Google File System. ACM SOSP, 2003, pp. 29–43. doi:10.1145/945445.945450
  • Dean, J. & Ghemawat, S. MapReduce: Simplified Data Processing on Large Clusters. USENIX OSDI, 2004.(GFS 一致性与追加语义的主要消费方,非本文对象)
  • DeCandia, G. et al. Dynamo: Amazon's Highly Available Key-value Store. ACM SOSP, 2007.(对照另一种放松:永远可写与向量时钟)

延伸阅读

  • McKusick, M. K. & Quinlan, S. GFS: Evolution on Fast-forward. ACM Queue 7(7), 2009.(事后访谈式回顾,含后续演进线索,细节以 2003 年论文为准)
  • 与 文件系统 对读:POSIX 命名与块缓存解决的是单机寿命;GFS 解决的是千台商品机上的追加与故障。
  • 与 大数据系统 对读:总览里的 GFS 一句话,对应的是本文拆开的主节点、大块、放置与一致性表。