适合学习和回忆的 GFS 中 Client 向 Master 请求元数据、再与 ChunkServer 通信的全过程。
读流程, 写流程,写流程比读复杂,涉及 primary / secondary / lease / 数据流水线。
GFS 中三类角色
Client:
发起读写请求的客户端Master:
只管理元数据,不直接传输文件数据
管理:
- 文件名空间
- 文件 -> chunk 映射
- chunk 副本位置
- chunk lease(谁是 primary)ChunkServer:
真正存储 chunk 数据
一、先记住最核心原则
GFS 的关键设计思想
Master 不在数据通路上,只在控制通路上。
也就是:
- Master 负责告诉 Client 去哪里读/写
- 真正的数据传输发生在 Client 和 ChunkServer 之间
这个是 GFS 最重要的一句。
二、GFS 读数据全过程
假设用户要读取:
/foo.txt 的某个 offset 开始的一段数据
1)读流程总图
2)逐步解释
第一步:Client 先根据文件名和 offset 定位 chunk
GFS 文件被切成很多固定大小的 chunk(经典论文里通常是 64MB)。
所以 Client 读取时,先要算出:
chunk index = offset / chunk_size
比如:
读取 /foo.txt 的 offset = 130MB
chunk_size = 64MB
那么它要找的就是:
第 2 号 chunk
第二步:Client 向 Master 请求元数据
Client 不会直接问 “把数据给我”。
它问的是:
给我 /foo.txt 第 i 个 chunk 的:
- chunk handle
- 这个 chunk 的副本位置
Master 返回:
chunk handle = 12345
replicas = [ChunkServer A, B, C]
这里:
- chunk handle 是 chunk 的全局唯一标识
- replicas 是这个 chunk 的多个副本所在的 ChunkServer
第三步:Client 直接选一个 ChunkServer 读
Client 拿到副本列表后,会自己选一个 ChunkServer,通常选:
- 距离更近的
- 网络更快的
- 当前看起来更合适的
然后直接发请求:
请把 chunk handle=12345 从某个内部 offset 开始的数据发给我
注意这里已经 不经过 Master 了。
第四步:ChunkServer 返回数据
ChunkServer 找到本地 chunk 数据,返回给 Client。
如果这个请求只覆盖一个 chunk,流程结束。
如果读跨越多个 chunk,Client 会:
- 对下一个 chunk 再发请求
- 如果缓存里没有元数据,再去问 Master
3)读流程一句话总结
Client 先向 Master 要“位置”,再直接向 ChunkServer 要“数据”。
三、GFS 读流程的细节补充
1. Client 会缓存元数据
Master 负担不能太重,所以 Client 会缓存:
- 文件到 chunk 的映射
- chunk handle
- chunk 副本位置
所以后续连续读常常是:
第一次问 Master
后面直接找 ChunkServer
2. Master 不返回数据本身
Master 只返回:
- chunk handle
- 副本位置
它不转发真实数据。
3. 如果 ChunkServer 失败怎么办
如果 Client 发现某个 ChunkServer 不可用:
- 换另一个副本重试
- 如果元数据过期,再重新问 Master
四、GFS 写数据全过程
写流程比读复杂很多,因为要保证多个副本的一致性。
核心角色:
- Master:告诉 Client 哪些副本参与写,以及谁是 primary
- Primary ChunkServer:负责给这次写操作确定顺序
- Secondary ChunkServer:按 primary 指定的顺序执行写入
五、GFS 写流程总图
六、为什么写流程要分成“推数据”和“发写命令”两步
这是 GFS 很经典的设计。
第一步:数据先推送到所有副本
Client 会先把数据送到所有参与写入的 ChunkServer,但此时它们只是:
- 把数据暂存在内部缓冲区
- 还没有真正写到正式位置
第二步:再由 primary 发号施令
真正写的时候,Client 只向 primary 发送控制命令:
请把刚才那份数据写到 chunk 的某个位置
这样做的好处是:
- 数据传输和控制解耦
- primary 只负责排序,不必承载全部数据转发压力
- 写入顺序统一,便于副本一致
七、写流程详细展开
第 1 步:Client 向 Master 请求写元数据
Client 告诉 Master:
我要写 /foo.txt 的某个 chunk
Master 返回:
- 该 chunk 的各个副本位置
- 哪个副本当前是 primary
- 对应 lease 信息
例如:
chunk handle = 12345
primary = ChunkServer A
secondaries = [B, C]
第 2 步:Client 把数据推送给所有副本
Client 不会只把数据发给 primary。
而是会把数据发送到全部副本。实际实现里常是 链式流水线:
例如:
Client -> A -> B -> C
或者按网络拓扑优化路径推送。
重点是:
- 每个 ChunkServer 都先收到同一份数据
- 数据先缓存起来
- 此时还没正式提交
第 3 步:Client 向 primary 发送写请求
当所有副本都拿到数据后,Client 向 primary 发控制请求:
write(chunk_handle, data_id, offset)
这里 data_id 表示刚才那份已经推送到各副本缓存区的数据。
第 4 步:Primary 决定全局顺序
这一步是 GFS 写一致性的关键。
多个 Client 可能同时往同一个 chunk 写,primary 要决定:
这次写是第几号操作
先执行谁,后执行谁
也就是:
primary 给这个 chunk 上的并发修改建立一个串行顺序。
第 5 步:Primary 把写命令转发给 secondaries
Primary 告诉 secondary:
按这个顺序执行这次写
Secondaries 接到后,在本地正式写入对应位置。
第 6 步:Secondaries 回 ACK 给 Primary
每个 secondary 写完之后返回确认。
第 7 步:Primary 汇总结果并回复 Client
如果所有副本都成功,primary 回复 Client 成功。
如果某些副本失败,Client 会收到错误,然后重试或重新询问 Master。
八、GFS 写流程的本质
数据先分发到所有副本,命令由 primary 排序后统一执行。
九、Append 流程和普通 Write 的区别
GFS 里还有一个非常有代表性的操作:Record Append。
它和普通写的区别是:
- 普通写:Client 指定 offset
- Record Append:Client 只说“把这条记录追加到文件末尾”
这时由 primary 决定最终追加位置。
Record Append 流程概念图
为什么 GFS 很强调 Append
因为 GFS 面向的典型场景是:
- 大量日志写入
- 追加式数据生成
- 批处理数据生产
所以 “追加” 比 “随机覆盖写” 更常见。
十、读写全过程总览图
十一、GFS 中 Master 到底干了什么
可以把 Master 记成:
Master 负责控制面
- 文件名空间管理
- 文件到 chunk 的映射
- chunk 副本位置管理
- 分配新的 chunk handle
- 选 primary,授予 lease
- 垃圾回收、重复制、负载均衡
Master 不负责数据面
- 不转发文件内容
- 不参与正常读写数据流
十二、“全过程简版”
读流程
1. Client 根据文件名和 offset 确定要访问哪个 chunk
2. Client 向 Master 请求 chunk handle 和副本位置
3. Master 返回 chunk 元数据
4. Client 直接联系某个 ChunkServer 读取数据
5. ChunkServer 返回数据给 Client
写流程
1. Client 向 Master 请求 chunk 的副本位置和 primary
2. Master 返回 primary 和 secondaries
3. Client 先把数据推送到所有副本
4. Client 向 primary 发送写命令
5. primary 确定并发写入顺序
6. primary 通知 secondaries 按相同顺序写入
7. secondaries 返回确认
8. primary 汇总后回复 Client
十三、一句总总结
你可以把 GFS 整个通信过程记成这两句:
读
先问 Master 位置,再找 ChunkServer 拿数据。
写
先问 Master 谁是 primary,再把数据发给副本,由 primary 统一排序提交。
