- 为了性能 (Performance), 所以将数据分割放到大量的服务器上,从而实现并行的读取数据,这就是分片 (Sharding).
- 而成败上千的机器总会发生错误,所以有了容错 (Fault Tolerance).
- 实现容错最简单的方式就是复制 (Replication), 其中一个发生故障了就切换另一个.
- 使用了复制,如果你不够小心,那么它们之间就可能会不一致。数据就有可能出现问题,所以就有了不一致的问题 (Inconsistency).
- 如果为了实现一致性 (Consistency), 那么就需要多进行额外的交互来保证一致性,所以代价就是低性能 (Low Perf)
, 但这与我们开始的希望不符合.
{{< block type="tip">}}
So, 强一致性代表着低性能.
{{< /block >}}
设计目标#
- 由于 GFS 是建立在大量的计算机上的,而这些计算机会不可避免的发生故障。所以必须要进行:检查,容错以及快速从故障恢复.
- 主要支持大文件(例如说好几个 G 的文件), 同时也支持小文件但不做针对性的优化.
- 工作负载主要由两种类型的读取组成:大的流式读取和小的随机读取
. 对于性能有过特别考虑的应用通常会作批处理并且对他们读取的内容进行排序,这样可以使得他们的读取始终是单向顺序读取,而不需要往回读取数据.- 在大的流式读取中,单个操作通常要读取数百 k, 甚至 1m 或者更大的数据。对于同一个客户端来说,往往会发起连续的读取操作顺序读取一个文件.
- 小的随机读取通常在某个任意的偏移位置读取几 kb 的数据。小规模的随机读取通常在文件的不同位置,读取几 k 数据.
- GFS 中的文件通常上一旦完成写入就很少会再次修改,所以主要针对大的流式读取, 同时夜支持任意位置的小规模写入操作.
- GFS 对多个客户端并行添加同一个文件必须要有非常有效且明确语义的支持,即原子操作. 通常会有多个客户端会并行的对同一个文件进行 append.
- 高性能的稳定带宽的网络要比低延时更加重要。我们大多数的目标应用程序都非常重视高速批量处理数据
, 而很少有人对单个读写操作有严格的响应时间要求.
架构#
- 单个 master, 多个 chunk server (保存具体的文件), 多个 client.
- 每个文件被拆分为一定大小 (64mb) 的块 (chunk), 且每个 chunk 有一个唯一的 64 位的标志 (chunk handle).
- 每个 chunk 都会在不同的 chunk server 上保存备份 (默认是 3 个), 用户可以指定不同的复制级别.
- master 管理元数据 (metadata), 例如文件到 chunk 的映射关系,chunk 的位置信息等.
- master 管理 chunk 的分片,孤点 chunk 的垃圾回收机制,chunk server 之间的镜像管理等
- 每个 chunk server 与 master 之间有心跳机制,并在检测的过程中年发出指令并收集状态.
GFS Master 中的 metadata#
- filename -> chunk ids(chunk handles) NV
- chunk handle 与 chunk 数据的对应关系
- chunk 保存在哪个服务器上 (chunk server list)
- chunk 的 version no NV
- chunk 的 primary chunk server, 因为写操作在在其上进行
- primary chunk server 的 lease expiration
这两个 data table 都在 master 的内存中存放,为了容错 (例如说重启后数据不丢失数据), 它会在磁盘上存储 log, 读取的使用从内存里面读取,写的时候会写入内存以及磁盘.
每当有数据变更时,就会在磁盘上的日志进行追加,并且定期 (日志增长超过某一个大小) 创建 checkpoint (类似快照,不用从头开始读取)
GFS Read Steps#
- 首先读请求就表明 client 有 filename 以及想要读取的位置 (offset), 然后发送给 master.
- master 收到请求后就从 filenames 中获取对应的 chunk handles. 而每个 chunk 的大小上固定的,所以就得到的具体开始的 chunk handle.
- 然后根据 chunk handle 找到对应存放数据的 chunk server 的列表返回给 client.
- client 可以选择一个 server 来进行读取 (论文中说会选择一个最近的服务器,应为 google 里面 ip 是连续的,可以根据 ip 判断远近)
, 应为客户端每次只读取 1mb 或者 64kb 的数据,所以它会缓存 chunk 与 chunk server 的关系,这样就不用每次都请求. - chunk server 收到请求后,根据 chunk handle (推测 chunk 是安装 chunk handle 进行命名的) 找到对应的 chunk 以及 offset 对应的数据给客户端.
q1: 如果读取的数据跨越了一个 chunk 怎么办?#
例如说 client 想要读取的数据超过了 64mb, 或者仅仅上是 2 个 byte 却跨越了 chunk,client 会在发送请求时注意到这次请求跨越了边界,
所以会将一个请求拆分为 2 个请求发送到 master, 所以这里可能上向 master 发送两次读请求,之后在向不同的 chunk server 读取数据.
多个副本之间变更顺序的一致性#
针对一个 chunk
- master 授权给某个持有这个 chunk 的 server 一个租约期限 (60s), 称为 primary.
- primary 对所有的更改操作进行排序 (serial order), 然后其他的 secondary 根据这个顺序进行变更.
- 只要这个 chunk 正在变更,那么 primary 就可以向 master 申请延长租约.
GFS Write Steps#
- client 向 master 发送请求获取 chunk server list (primary,secondaries),
如果没有 primary,master 就会选择一个 secondary 成为 primary. - client 获取到 chunk server list 后会缓存下来,只有当 primary
没有响应或租约过期后才会再次请求. - client 将数据推送到所有 replicas, 客户端不保证推送的顺序,每个 chunk server 会将数据保存在内部的 lur cache 中,直到数据被使用或过期.
- 当所有 replicas 都收到了数据,client 将会发送一个写请求到 primary, 它标识了之前推送到每个副本的数据.
primary 将这些写入组织成一定的顺序应用到自己本地. - primary 然后将这个应用顺序转发给各个 secondary.
- secondaries 应用这个顺序完成修改并答复 primary.
- primary 答复 client, 如果出现了任意错误也会答复给 client. 在出现错误的情况下,write request 也可能在 primary 以及 secondary 中成功
(如果 primary 直接就失败了,那么它将不会转发 serial order 给 secondaries),client 将认为这次请求是失败的,它会通过重试来处理 (
3-7 尝试几次重新写入)
GFS Atomic Record Appends#
{{<block type="tip" title="对同一片区域个并发写入是不可序列化的">}}
这片区域可能最终包含多个客户端的数据片段.
{{< /block >}}
一个原子的 append 操作.recored append
至少会在给定的 offset (GFS 自己选择的,因为这里可能会失败,可能有一些 chunk server 上有这个数据)
上追加到文件上一次,并将该 offset 返回给 client. 它类似O_APPEND
保证原子性.
recored append
遵守 GFS Write Steps 流程,但是有一些特别的地方:
- client 推送所有数据后,primary 会检查 append 到该 chunk 后是否超过了单个 chunk 的大小.
- 如果超过了,则在当前 chunk 填充到最大 offset 时 (secondary 也要保存), 回复 client, 指出该操作应该在下一个 chunk 上重试 (
record 的大小需要控制在单个 chunk 最大值的四分之一,以保证碎片在可接收的水平). - 如果没有超过最大大小,则按照正常的情况进行保存.
过期副本检测#
如果 chunk server 发生故障而宕机或者丢失了某些更新请求,那么它就有可能过期了。对于每个 chunk,master 都维护了一个 version
no 来标识最新和过期的副本.
当 master 为一个 chunk 的 primary server 授权或续期时就会增加 version no 并通知所有 replicas 进行更新.
在数据一致的情况下,master 和所有 replicas 的 version no 是一致的 (在 client 发送写请求之前可以保证).
当 chunk server 重启或上报 version no 时,master 会检查它时否包含过期的副本,如果发现 master 发现 version
no 大于它的记录,master 会采用更高的 version no 进行更新.
master 通过周期性的垃圾回收来删除过期的副本,在删除前,它会确认在它所有 client 的 chunk 信息请求的应答中没有包含这个过期的副本.
client 在从 master 获取 chunk server 列表时会附带获取 version no, 所以它可以进行比对,选择最新的副本进行操作.
总结#
这并不是一个合格的多副本,多活,高可用,故障自修复的分布式系统.