跳转到内容

GFS 2003 — 把廉价机器拼成大文件仓库

待复核

GFS(Google File System)是 Google 为海量网页数据处理做的一套分布式文件系统:很多便宜机器各存一部分数据,外面看起来像一个巨大的文件仓库。

日常类比:它不像一间精致档案室,要求每个柜子永远不坏;它更像一个大物流仓,默认货架会坏、搬运工会出错,所以每箱货要放三份,仓库长要随时调度补货。

这篇论文的关键不是“文件系统也能分布式”,而是重新定义文件系统该服务谁。GFS 服务的不是普通桌面小文件,而是搜索引擎背后的批处理:文件很大、顺序读多、追加写多、机器经常坏。

所以 GFS 主动放弃完整 POSIX 语义,把设计押在大 chunk、单 master、三副本、追加写和自动修复上。

不理解 GFS,下面这些事都不好解释:

  • 为什么 hdfs-2010 几乎被称为“开源版 GFS”:NameNode、DataNode、大块、三副本都能在这里找到源头。
  • 为什么大数据平台偏爱“写一次、读很多次”:这个假设能换来简单的恢复、顺序吞吐和低元数据成本。
  • 为什么对象存储、日志系统、批处理框架常常不追求 POSIX:通用语义太贵,吞吐系统会为工作负载定制接口。
  • 为什么单 master 不一定是坏设计:只要 master 不搬数据,只管元数据,它可以简单又够快。

GFS 可以抓住三句话:

  1. 控制流走 master,数据流绕开 master。类比:仓库长告诉你货在哪个货架,但不会亲自搬货。客户端问 master 拿 chunk 位置,然后直接找 chunkserver 读写数据。

  2. 64 MB 大 chunk 降低管理成本。类比:把货装成大箱子,而不是给每颗螺丝单独编号。chunk 越大,master 要记的条目越少,客户端找 master 的次数也越少。

  3. 故障是日常,不是意外。类比:仓库每天都有货架坏,所以系统不靠祈祷机器稳定,而靠心跳、校验和、三副本、重复制和垃圾回收持续修补。

这三点合起来,就是“用便宜硬件换规模,用放宽语义换吞吐”。

案例 1:读一个文件块时谁在干活

Section titled “案例 1:读一个文件块时谁在干活”
def gfs_read(file, offset, length):
chunk = offset // CHUNK_SIZE
handle, replicas = master.lookup(file, chunk)
server = choose_nearest(replicas)
return server.read(handle, offset % CHUNK_SIZE, length)

逐部分解释:

  • master.lookup 只返回元数据:chunk 句柄和副本位置。
  • 真正的数据从 server.read 读取,不经过 master。
  • 客户端会缓存 chunk 位置,后续顺序读可以少问 master。

案例 2:写入时为什么要有 primary

Section titled “案例 2:写入时为什么要有 primary”
def gfs_write(chunk, data):
primary, secondaries = master.find_lease_holder(chunk)
push_data_to_all([primary, *secondaries], data)
serial = primary.assign_order(data)
for server in secondaries:
server.apply(serial, data)
primary.apply(serial, data)

逐部分解释:

  • master 给某个副本发 lease,让它临时当 primary。
  • primary 决定同一个 chunk 上多个写入的顺序。
  • secondary 按同样顺序执行,所以副本状态尽量保持一致。

案例 3:record append 让很多机器一起写

Section titled “案例 3:record append 让很多机器一起写”
def record_append(file, record):
last_chunk = master.last_chunk(file)
offset = primary_append_at_gfs_chosen_offset(last_chunk, record)
if failed_or_full(offset):
return record_append(file, record)
return offset

逐部分解释:

  • 客户端不指定写入偏移,而是把“放哪里”交给 GFS。
  • GFS 保证一条 record 至少完整出现一次,但可能有 padding 或重复。
  • 上层应用要给 record 带 checksum 和唯一 id,读的时候过滤坏片段和重复记录。
  1. 以为 GFS 是通用文件系统:它不是 POSIX 替代品,而是为 Google 批处理工作负载定制的吞吐系统。
  2. 以为单 master 一定扛不住:master 不传文件数据,只管理 namespace、chunk 映射和 lease,热点被大 chunk 和客户端缓存稀释。
  3. 忽略 relaxed consistency:并发写可能得到 consistent but undefined 的区域,应用必须用追加、checkpoint、checksum 自己收口。
  4. 只看到三副本,没看到修复循环:可靠性来自心跳、校验、版本号、重复制、垃圾回收一起工作,不只是多存两份。

适用

  • 大文件、顺序读、批处理吞吐优先的系统。
  • 追加写多、覆盖写少、可以容忍应用层去重和校验的场景。
  • 机器数量很多、单机不可靠、需要自动修复副本的集群。
  • 存储层和计算框架可以一起设计,比如搜索索引、日志归并、离线分析。

不适用

  • 小文件特别多、低延迟随机读写为主的在线服务。
  • 需要严格 POSIX rename、锁、缓存一致性和随机覆盖写的传统应用。
  • 强事务数据库主存储,尤其是多行更新和毫秒级写延迟敏感场景。
  • 团队规模很小但想自研全套分布式存储,运维成本会先打败收益。
  • 1990s 末:Google 的网页抓取、索引和排序开始产生越来越大的中间数据,普通文件系统假设不够用了。
  • 2003 年:GFS 在 SOSP 发表,公开“便宜机器 + 大文件 + 单 master + chunkserver”的工业设计。
  • 2004 年mapreduce 论文接着发表,默认底层能提供这种大文件顺序吞吐,GFS 成了计算框架的地基。
  • 2006 年bigtable-2006 发表,把结构化存储建在 GFS 之上,形成 Google 早期数据平台三件套。
  • 之后:HDFS、对象存储、Colossus、云存储系统都继承或反思了 GFS 的取舍。
  1. 系统设计先看工作负载:GFS 的每个取舍都来自“大文件、追加写、顺序读、机器常坏”。
  2. 简单中心化可以很强:单 master 让副本放置、垃圾回收、重复制策略简单很多,只要别让它搬数据。
  3. 语义可以下放给应用:GFS 放宽一致性,上层用 checkpoint、record id、checksum 把结果整理成可用数据。
  4. 可靠性是持续过程:不是写入成功就结束,而是后台一直巡检、修复、清理和重新平衡。
  • 论文页面:The Google File System(官方入口,重点看 1、2、3、5 节)
  • 论文 PDF:GFS SOSP 2003(15 页,适合配合架构图读)
  • hdfs-2010 —— Hadoop 把 GFS 思路开源化,成为大数据学习入口。
  • mapreduce —— 典型消费者:大量任务顺序读写 GFS 上的大文件。
  • bigtable-2006 —— 建在 GFS 之上的结构化存储,继续使用大块和追加式思路。
  • azure-storage-2011 —— 云对象存储把类似分层思路服务化,并加强一致性和多租户。
  • hdfs-2010 —— HDFS 继承 GFS 的 NameNode/DataNode、大块、三副本和批处理取向。
  • mapreduce —— MapReduce 需要 GFS 提供可并行扫描的大文件输入和中间结果存储。
  • bigtable-2006 —— Bigtable 的 tablet 和 SSTable 存在 GFS 上,是“文件系统之上的数据库”。
  • spanner-2012 —— Spanner 后来走向全球事务,和 GFS 同属 Google 存储谱系但目标不同。
  • haystack-2010 —— Haystack 同样从工作负载出发,但优化的是海量小图片随机读。
  • azure-storage-2011 —— Azure Storage 借鉴 chunk/stream 分层,同时用更强复制协议托管云服务。
  • scale-performance-distributed-file-system-1988 —— AFS 是 GFS 对照对象,代表更传统的分布式文件系统路线。