IT 论文精读 · PAPER 17
Ghemawat, Gobioff & Leung · Google · SOSP 2003
2003 年,Google 的三位工程师公开了他们自己造的一套存文件的系统——GFS。它要解决的事很实在:Google 的搜索、爬下来的整个网页库、日志……全都大得离谱,一台机器根本装不下,得摊到成千上万台便宜机器上一起存。GFS 就是把这几千台机器粘成「一块超级大硬盘」的那层软件。它后来成了 MapReduce、Bigtable 的地基,也直接催生了开源的 Hadoop。
他们造这套系统时认准一件事:机器坏,是常态,不是意外。几千台廉价机器摆在一起,每天都有硬盘冒烟、电源挂掉、网线松动——不是「会不会坏」,而是「此刻正有几台坏着」。传统存储系统把「硬件基本可靠」当默认前提,到这个规模就彻底不成立了。GFS 的全部设计,都是从「随时有东西在坏」这个假设倒推出来的。
GFS 的安排出奇地简单,就两种角色:一个「总管」(master)和一大群「仓库工」(chunkserver)。
关键的取巧在于:总管只管账本,从不碰货。真正的文件数据被切成一个个大块,每块存三份、扔在三台不同的机器上;总管手里只有一本「账」——哪个文件由哪些块组成、每块的三份分别在谁那儿。你要读文件,先问总管「东西在哪」,总管报个地址,你就直接去那台仓库工机器搬货,不再经过总管。
几个朴素但管用的决定。一、切成大块。文件按 64 MB 一大块来切(而不是按几 KB 的小格子),账本一下就小了很多——总管用一台机器的内存就能把整个系统的账记下。二、每块存三份。一台机器挂了,另两份还在;总管发现哪块少了一份,会悄悄再复制一份补齐,你几乎无感。三、总管不碰数据。所有搬货都是客户端和仓库工「点对点」直连,总管永远不会因为「所有流量都从我这过」而堵成瓶颈。四、为「往后追加」优化。Google 的活儿大多是「把新数据不断加到文件末尾」(比如源源不断的日志),GFS 专门把「很多人同时往一个文件末尾追加」这件事做得又快又安全。
GFS 第一次让人相信:用一堆随时会坏的廉价机器,真能拼出一个又大、又扛造、还好用的存储系统——靠的不是让每台机器更可靠,而是让整套系统「容忍机器坏」。这套「便宜机器 + 多副本 + 软件兜底」的路子,成了此后十几年大数据基础设施的通用模板。
把几千台会坏的廉价机器粘成一块超级大硬盘:数据切成大块、每块存三份、机器坏了自动补;一个「总管」只记账不碰货,读写都由客户端和仓库工直连——于是系统又大、又扛坏、又不堵。这是大数据时代存储的第一块地基。
想看它的架构图、租约与追加写的机制、还有真实集群的数字? → 切到精读版
GFS 是 Google 为自己的超大规模工作负载量身定做的分布式文件系统:它把「硬件故障是常态」当作设计出发点,用单一 master 管元数据 + 大量 chunkserver 存数据的架构,把文件切成 64 MB 的大块、每块默认存三副本,并靠租约(lease)、原子记录追加(atomic record append)、松弛一致性模型把控制与数据流彻底解耦——用一群随时会坏的廉价机器,稳定支撑起 PB 级、以「大文件顺序读 + 追加写」为主的存储。它是 MapReduce、Bigtable 的底座,也直接启发了开源的 HDFS。
作者是 Sanjay Ghemawat、Howard Gobioff、Shun-Tak Leung,来自 Google,论文发表于 SOSP 2003。它是 Google「分布式系统老三件套」的第一件——次年的 MapReduce(大规模并行计算)和两年后的 Bigtable(结构化存储)都直接建在它之上。它上承传统分布式文件系统(如 AFS、xFS),却大胆抛掉「通用 / POSIX 兼容」的包袱、只服务 Google 自己的工作负载;下启开源世界的 HDFS(Hadoop 的文件系统,几乎是 GFS 的公开复刻),并在 Google 内部逐步演进为更强的 Colossus。
2000 年代初,Google 要处理的数据量超出当时任何现成存储方案能优雅承受的范围。团队没有硬套通用文件系统,而是先老老实实观察自己的工作负载长什么样,得出四条与传统假设截然不同的前提:
一句话:别造一个通用的完美文件系统,造一个正好匹配这批工作负载、且默认硬件天天坏的文件系统。
GFS 集群里只有两类角色。单一 master 掌管所有元数据:命名空间(目录树)、访问控制、每个文件由哪些块组成、以及每个块的三份副本当前在哪些机器上。大量 chunkserver 才是真正存数据的地方。
文件被切成固定大小的块(chunk),每块 64 MB,创建时由 master 分配一个全局唯一的 64 位块句柄(chunk handle)。每个块作为一个普通 Linux 文件存在 chunkserver 的本地磁盘上,并默认复制 3 份、分散到不同机器(且尽量跨机架)。
最关键的设计是「账货分离」:master 只管账、绝不经手数据。客户端读文件时,先拿「文件名 + 块序号」问 master「这个块的副本都在哪」,master 回一串 chunkserver 地址(客户端会缓存一阵);之后客户端直接找 chunkserver 搬数据,完全绕开 master。这样 master 就不会因为「所有数据都从我流过」而变成带宽瓶颈,一台 master 就能服务整个集群。
为什么块要那么大(64 MB)?好处有三:① 大块 → 块的总数少 → 元数据小,master 能把整份元数据放进内存,查得飞快;② 客户端一次问址能读写很大一段,大幅减少和 master 打交道的次数;③ 客户端在一个块上会做很多次操作,可复用与 chunkserver 的连接。代价(诚实说):小文件只占一两个块,容易变成热点——很多客户端同时挤向同一台 chunkserver。
master 把三类元数据全放在内存:命名空间、文件→块的映射、每块的副本位置。前两类会持久化——写进一份操作日志(operation log),这份日志既是崩溃恢复的依据,也定义了并发操作的逻辑先后顺序;日志会复制到多台远程机器,master 重启时重放日志即可恢复状态(并定期做检查点 checkpoint 压缩重放量)。
唯独「块的副本位置」不写进日志——因为 chunkserver 才是这件事的权威。master 启动时以及之后靠心跳(HeartBeat)反复问每台 chunkserver「你手上有哪些块」。这么做省去了「master 和 chunkserver 状态不一致」的一大类麻烦:机器加入、退出、改名、重启是家常便饭,与其费劲维护一致,不如让 chunkserver 说了算、master 定期同步。
一个块有三份副本,多个客户端还可能同时写——怎么保证三份最后内容一致?GFS 的答案是租约(lease):master 把某个块的一份短期「话事权」(租约,约 60 秒、可续)交给其中一个副本,称为主副本(primary)。之后这个块上所有变更的先后顺序,由 primary 一家说了算,其余副本照着同样的顺序执行。这样一来,master 完全不必参与每一次写——它只发一次租约,把「给变更排序」这件重活下放给 primary,自己继续轻装管元数据。
写一个块时,GFS 把「指挥」和「搬运」拆开走两条路。数据不走星形(都推给 primary 再分发),而是沿一条链流水线传递:客户端只把数据推给最近的一个副本,它一边收一边转发给链上下一个,如此接力。这样每台机器的出口带宽都只用来发给「下一个」、不被瓜分,网络利用率拉满。数据在各副本内存里备好后,客户端才向 primary 发一个很小的「写」控制指令,primary 定序、通知各副本按序落盘。数据流按网络远近选路、控制流走 primary 定序,两者解耦——这是 GFS 吞吐高的关键工程细节。
这是 GFS 最有特色、也最贴合 Google 工作负载的操作。传统「写到指定偏移量」在多客户端并发时要么加锁、要么互相覆盖。GFS 提供原子记录追加(record append):客户端只说「把这段数据加到文件末尾」,由 GFS 自己挑一个偏移量写入、再把该偏移量告诉客户端。于是成百个客户端能同时往同一个文件追加、无需额外加锁——非常适合「多个生产者往一个结果 / 队列文件里汇数据」的场景。
代价是它只保证「至少写入一次(at-least-once)」:某副本追加失败时客户端会重试,可能留下重复记录或填充空隙(padding)。GFS 把这点复杂性推给应用——应用用校验和、唯一 ID 去重、跳过填充即可。这正是「应用与文件系统协同设计」的体现:放宽一点保证,换来系统整体的简单与高并发。
GFS 不追求强一致,而是给出一个「松弛但够用」的模型。元数据操作(如创建文件)由 master 用命名空间锁保证原子。对文件数据区域,成功变更后可能是:consistent(一致)——所有客户端无论读哪份副本都看到相同数据;defined(已定义)——不仅一致,还能看到这次变更的完整结果。并发的普通写可能「一致但未定义」(各写都在、但交错难辨);记录追加则保证「已定义」,但中间可能夹着重复或填充。关键取舍:用更弱的一致性承诺,换取在故障频发的大集群上依然能跑得又快又稳。
论文用微基准和两个真实生产集群说话。真实集群各有数百台 chunkserver、上百 TB 磁盘、几十万到上百万个文件,日常承载 Google 内部的研发与生产负载。数据显示:读远多于写、追加远多于覆盖写——与设计假设高度吻合;聚合读吞吐可达每秒数百 MB 量级,接近网络上限。恢复实验也很有说服力:杀掉一台 chunkserver、其上数千个块副本,master 能在几十分钟内把副本数全部补齐,期间服务不中断。更重要的结论是定性的——用廉价、天天坏的机器,真的能拼出一个大规模、高吞吐、扛得住故障的存储系统,且它已在 Google 内部大规模上线、支撑真实业务。
GFS 是大数据基础设施的第一块地基。次年的 MapReduce、两年后的 Bigtable 都直接建在它之上,构成 Google「老三件套」;开源世界几乎照它复刻出 HDFS,撑起了整个 Hadoop 生态,进而影响了此后十余年几乎所有大数据系统的设计。它真正确立并普及了一整套思想:把可靠性从「单机硬件」层面上移到「软件 + 多副本」层面、用大量廉价机器横向扩展、元数据与数据分离、为具体工作负载放宽通用语义。今天你听到的「分布式存储」「副本容错」「主从架构」,很多默认套路都能追到这篇。
① 一句话:为超大规模、以「大文件顺序读 + 追加写」为主的负载设计的分布式文件系统,把「硬件故障是常态」当出发点。
② 架构:单一 master 管元数据 + 大量 chunkserver 存数据;文件切成 64 MB 大块、每块存 3 副本、跨机架分散。
③ 账货分离:master 只回答「块在哪」,数据由客户端与 chunkserver 直连搬运,master 永不成为数据瓶颈;大块 → 元数据小 → 可全放内存。
④ master 元数据在内存,靠操作日志 + 检查点持久化与恢复;块位置不持久化,靠 chunkserver 心跳上报。
⑤ 租约把「给变更排序」下放给 primary,master 退出写路径;数据流沿链流水线推送、控制流走 primary 定序,两者解耦以拉满带宽。
⑥ 原子记录追加让多客户端并发追加无需加锁,但只保证「至少一次」(可能重复 / 填充),复杂度推给应用。
⑦ 一致性松弛但够用(consistent / defined),元数据操作原子;容错靠再复制、校验和、版本号、快速恢复、影子 master。
⑧ 影响:MapReduce / Bigtable 的底座,启发 HDFS,确立「廉价机器 + 多副本 + 软件容错 + 横向扩展」的大数据范式。
⑨ 局限:单 master 扩展瓶颈、小文件不友好、松弛一致外包复杂度、非通用;Google 自己以 Colossus 接棒。