从 GFS 失败的架构设计来看一致性的重要性

作者简介 陈东明,饿了么北京技术中心架构组负责人,负责饿了么的产品线架构设计以及饿了么基础架 构研发工作。曾任百度架构师,负责百度即时通讯产品的架构设计。具有丰富的大规模系统构 建和基础架构的研发经验,善于复杂业务需求下的大并发、分布式系统设计和持续优化。作者微信公众号dongming_cdm,欢迎关注。

GFS(Google File System)是Google公司开发的一款分布式文件系统。

在2003年,Google 发表一篇论文详细描述了GFS的架构。GFS,MapReduce,Bigt able并称为Google的三架⻢ ⻋,推动了Google的高速发展。其他互联公司和开源领域纷纷模仿,构建自己的系统。可以 这么说,GFS,MapReduce,Bigt able引领了互联网公司的分布式技术的发展。但GFS架构设 计并不是一个完美的架构设计,它有诸多方面的问题,一致性的欠缺就是其中的一个问题。

本文探讨一下GFS架构设计、分析其在一致性方面的设计不足,并且看一下一致性对分布式系统 的重要性。

我们从GFS的接口设计说起。

接口

GFS采用了人们非常熟悉的接口,但是被没有实现如POSIX的标准接口。通常的一些操作包 括:create, delete, open, close, read, write, record append等。create,delete,open,close 和POSIX的接口类似,这里就不强调说了。这里详细讲述一下write, record append提供的语意。

  • write操作可以将任意⻓度len的数据写入到任意指定文件的位置off set
  • record append操作可以原子的将len<=16MB的数据写入到指定文件的末尾

之所以GFS设计这个接口,是因为record append不是简单的offset等于文件末尾的write操作。 record append是具有原子特性的操作,本文后面会详细解释这个原子特性。

write和record append操作都允许多个客户端并发操作。

架构

GFS架构如下图。(摘自GFS的论文)

主要的架构部件有GFS client, GFS master, GFS chunkserver。一个GFS集群包括:一个 master,多个chunkserver,集群可以被多个GFS client访问。

GFS客户端(GFS client)是运行在应用(Application)进程里的代码,通常以SDK形式 存在。

GFS中的文件被分割成固定大小的块(chunk),每个块的⻓度是固定64MB。GFS chunkserver把这些chunk存储在本地的Linux文件系统中,也就是本地磁盘中。通常每个 chunck会被保存3个副本(replica)。一个chunkserver会保存多个块,每个块都会有一个 标识叫做块柄(chunk handle)

GFS 主(mast er)维护文件系统的元数据(met adat a),包括名字空间(namespace, 也就是常规文件系统的中的文件树),访问控制信息,每个文件有哪些chunk构成,chunk 存储在哪个chunkserver上,也就是位置(locat ion)。

在这样的架构下,文件的读写基本过程简化、抽象成如下的过程:

写流程

1.client 向mast er发送creat e请求,请求包含文件路径和文件名。mast er根据文件路径和文件 名,在名字空间里创建一个对象代表这个文件。

2.client 向3个chunkserver发送要写入到文件中的数据,每个chunkserver收到数据后,将数据 写入到本地的文件系统中,写入成功后,发请求给mast er告知mast er一个chunk写入成功, 与此同时告知client 写入成功。

3.mast er收到chunkserver写入成功后,记录这个chunk与机器之间的对应关系,也就是chunk 的位置。

4.client 确认3个chunkserver都写成功后,本次写入成功。

这个写流程是一个高度简化抽象的流程,实际的写流程是一个非常复杂的流程,要考虑到写入 类型(即,是随机写还是尾部追加),还要考虑并发写入,后面我们继续详细的描述写流程, 解释GFS是如何处理不同的写入类型和并发写入的。

读流程

1.应用发起读操作,指定文件路径,偏移量(off set )

2.client 根据固定的chunk大小(也即64MB),计算出数据在第几个chunk上,也就是chunk索 引号(index)

3.client 向mast er发送一个请求,包括文件名和索引号,mast er返回3个副本在哪3台机器上, 也就是副本位置(location of replica)。

4.client 向其中一个副本的机器发送请求,请求包换块柄和字节的读取范围。 

5.chunkserver根据块柄和读取范围从本地的文件系统读取数据返回给client

这个读流程未做太多的简化和抽象,但实际的读流程还会做一些优化,这些优化和本文主题关 系不大就不展开了。

写流程详述

我们详细的讲一下写入流程的几个细节。

1.名字空间管理(namespace management)和锁保护(locking)

写入流程需要向主发送creat e这样请求,来操作保存在主上的名字空间。 如果有多个客户端同时进行写入操作,那么这些客户端也会同时操作向主发送creat e请求。主 在同一时间收到多个请求,通过加锁的方式,防止多个客户端同时修改同一个文件的元数据。

2.租约(Lease)

client 需要向3个副本写入数据,在并发的情况下会有多个client 同时向3个副本写入数据。GFS 需要一个规则来管理这些数据的写入。 这个规则简单来讲,每个chunk都只有一个副本来管理多个client 的并发写入。也就是说,对 于一个chunk,mast er会授予其中一个副本一个租约(lease),具有租约的副本来管理所有要写 入到这个chunk的数据写入,这个具有租约的副本称之为首要副本(primary)。其他的副本称之 为二级副本(secondary)

3.变更次序(Mutation Order) 

将对文件的写入(不管是在任意位置的写入,还是在末尾追加)称之为变更(Mut at ion)。 Primary管理所有client 的并发请求,让所有的请求按照一定顺序(Order)应用到chunk上。

4.基本变更流程

1).client 询问mast er哪个chunkserver持有这个chunk的lease,以及其他的副本的位置。如果 没有副本持有lease,mast er挑选一个副本,通知这副本它持有lease。

2).mast er回复client ,告述客户端首要副本的位置,和二级副本的位置。客户端联系首要副 本,如果首要副本无响应或者回复客户端它不是首要副本,则客户端重新联系主。 

3).客户端向所有的副本推送数据。客户端可以以任意的顺序推送。每个chunkserver会缓存这 些数据在缓冲区中。 

4).当所有的副本都回复说已经收到数据后,客户端发送一个写入请求(write request)给首 要副本,这个请求里标识着之前写入的数据。首要副本收到请求后,会给写入分配一个连续的 编号,首要副本会按照这个编号的顺序,将数据写入到本地磁盘。 

5).首要副本将这个带有编号写入请求转发给其他二级副本,二级副本也会按照编号的顺序, 将数据写入本地,并且回复首要副本数据写入成功。 

6).当首要副本收到所有二级副本的回复时,说明这次写入操作成功。 

7).首要副本回复客户端写入成功。在任意一个副本上遇到的任意错误,都会报告给客户端失败。

前面讲的writ e接口就是按照这个基本流程进行的。 下图描述了这个基本过程。(摘自GFS的论文)

5.跨边界变更 

如果一次写入的数据量跨过了一个块的边界,那么客户端会把这次写入分解成向多个chunk的 多个写入。

6.原子记录追加(Atomic Record Appends)

Record Appends在论文中被称之为原子记录追加(Atomic Record Appends),这个接口也 遵循基本的变更流程,有一些附加的逻辑:客户端把数据推送给所有的副本后,客户端发请求 给首要副本,首要副本收到写入请求后,会检查如果把这个record附加在尾部,会不会超出块 的边界,如果超过了边界,它把块中剩余的空间填充满(这里填充什么并不重要,后面我们会 解释这块),并且让其他的二级副本做相同的事,再告述客户端这次写入应该在下一块重试。 如果记录适合块剩余的空间,则首要副本把记录追加尾部,并且告述其他二级副本写入数据在 同样的位置,最后通知客户端操作成功。

原子性

讲完架构和读写流程,我们开始分析GFS的一致性,首先从原子性开始分析。

Write和Atomic Record Append的区别 前面讲过,如果一次写入的数量跨越了块的边界,那么会分解成多个操作,writ e和record append在处理数据跨越边界时的行为是不同的。我们举例2个例子来说明一下。

例子1,文件目前有2个chunk,分别是chunk1, chunk2。

Client 1要在54MB的位置写入20MB数据。这写入跨越了边界,要分解成2个操作,第一个操 作写入chunk1最后10 MB,第二个操作写入chunk2的开头10MB。Client 2也要在54MB的位置写入20MB的数据。这个写入也跨越边界,也要分解为2个操作, 作为第三个操作写入chunk1最后10 MB,作为第四个操作写入chunk2的开头10MB。

2个客户端并发写入数据,那么第一个操作和第三个操作在chunk1上就是并发执行的,第二个 操作和第四个操作在chunk2上并发执行,如果chunk1的先执行第一操作再执行第三个操作, chunk2先执行第四个操作再执行第二个操作,那么最后,在chunk1上会保留client 1的写入的 数据,在chunk2上保留了client 2的写入的数据。虽然client 1和client 2的写入都成功了,但最 后既不是client 1想要的结果,也不是client 2想要的结果。最后的结果是client 1和client 2写入 的混合。对于client 1和client 2来说,他们操作都不是原子的。

例子2,文件目前有2个chunk,分别是chunk1, chunk2。

Client 要在54MB的位置写入20MB数据。这写入跨越了边界,要分解成2个操作,第一个操作 写入chunk1最后10 MB,第二个操作写入chunk2的开头10MB。chunk1执行第一个操作成功 了,chunk2执行第二个操作失败了,也就是写入的这部分数据,一部分是成功的,一部分是 失败的,这也不是原子操作。

接下来看record append。由于record append最多能写入16MB的数据,并且当写入的数据量 超过块的剩余空间时,剩余的空间会被填充,这次写入操作会在下个块重试,这2点保证了 record append操作只会在一个块上生效。这样就避免了跨越边界分解成多个操作,从而也就 避免了,写入的数据一部分成功一部分失败,也避免了并发写入数据混合在一起,这2种非原 子性的行为。

GFS原子性的含义 

所以,GFS的原子性不是指多副本之间原子性,而是指发生在多chunk上的多个操作的的原子性。可以得出这样的推论,如果Writ e操作不跨越边界,那么没有跨越边界的writ e操作也满足GFS 所说的原子性。

GFS多副本不具有原子性 

GFS一个chunk的副本之间是不具有原子性的,不具有原子性的副本复制,它的行为是:

一个写入操作,如果成功,他在所有的副本上都成功,如果失败,则有可能是一部分副本 成功,而另外一部分失败。

在这样的行为如下,失败是这样处理的:

  • 如果是write失败,那么客户端可以重试,直到write成功,达到一致的状态。但是如果在 重试达到成功以前出现宕机,那么就变成了永久的不一致了。
  • Record Append在写入失败后,也会重试,但是与writ e的重试不同,不是在原有的off set 上重试,而是接在失败的记录后面重试,这样Record Append留下的不一致是永久的不一 致,并且还会有重复问题,如果一条记录在一部分副本上成功,在另外一部分副本上失 败,那么这次Record Append就会报告给客户端失败,并且让客户端重试,如果重试后成 功,那么在某些副本上,这条记录就会成功的写入2次。

我们可以得出,Record Append保证是至少一次的原子操作(at least once atomic)。

一致性

GFS把自己的一致性称为松弛的一致性模型(relaxed consistency model)。这个模型分析元 数据的一致性和文件数据的一致性,松弛主要是指文件数据具有松弛的一致性。

元数据的一致性
元数据的操作都是由单一的mast er处理的,并且操作通过锁保护,所以是保证原子的,也保 证正确性的。

文件数据的一致性 

在说明松弛一致性模型之前,我们先看看这个模型中的2个概念。对于一个文件中的区域:

  • 如果无论从哪个副本读取,所有的客户端都能总是看到相同的数据,那么就叫一致的 (consist ent );
  • 在一次数据变更后,这个文件的区域是一致的,并且客户端可以看到这次数据变更写入的 所有数据,那么就叫界定的(defined)。

GFS论文中,用下面的这个表格总结了松弛一致性:

WriteRecord Append
Serial successdefineddefined interspersed with inconsistent
Concurrent successesconsistent but undefined
Failureinconsistent

分别说明表中的几种情况:

1.在没有并发的情况下,写入不会有相互干扰,成功的写入是界定的,那么必然也就是一致的 

2.在有并发的情况下,成功的写入是一致的,但不是界定的,也就是我们前面所举的例子2。 

3.如果写入失败,那么副本之间就会出现不一致。

4.Record Append能够保证是界定的,但是在界定的区域之间夹杂着一些不一致的区域。 Record Append会填充数据,不管每个副本是否填充相同的数据,这部分区域都会认为是 inconsist ent 的。

如何适应松弛的一致性模型

这种松弛的一致性模型,实际上是一种不能保证一致性的模型,或者更准确的说是一致性的数 据中间夹杂不一致数据。

这些夹杂其中的不一致数据,对应用来说是不可接受的。在这种一致性下,应该如何使用GFS 那?GFS的论文中,给出了这样几条使用GFS的建议:依赖追加(append)而不是依赖覆盖 (overwrite),设立检查点(checkpointing),写入自校验(write self-validating),自记录标识 (self-identifying records)。

使用方式1:只有单个写入的情况下,按从头到尾的方式生成文件。 

  • 方法1.1:先临时写入一个文件,再全部数据写入成功后,将文件改名成一个永久的名 字,文件的读取方只能通过永久的文件名访问到这个文件。 
  • 方法1.2:写入方按一定的周期,写入数据,写入成功后,记录一个写入进度检查点 (checkpoint ),这个检查点包括应用级的校验数(checksum)。读取方只去校验、处理检查点之前的数据。即便写入方出现宕机的情况,重启后的写入方或者新的写入方,会 从检查点开始,继续重新写入数据,这样就修复了不一致的数据。

使用方式2:多个写入并发向一个文件尾部追加,这个使用方式就像是一个生产消费型的消息 队列,多个生产者向一个文件尾部追加消息,消费者从文件读取消息

方法2.1:使用record append接口,保证数据至少被成功的写入一次。但是应用需要应对 不一致数据,和重复数据。

  • 为了校验不一致数据,给每个记录添加校验数(checksum),读取方通过校验数识别 出不一致的数据,并且丢弃不一致的数据。
  • 如果应用读取数据没有幂等处理,那么应用就需要过滤掉重复数据。写入方写入记录时 额外写入一个唯一的标识(ident ifier),读取方读取数据后通过标识辨别之前是否已经 处理过这个标识的数据。

GFS的设计哲学

可以看出基于GFS的应用需要通过一些特殊的手段来应对GFS松弛的一致性模型带来的各种问 题。GFS的一致性保证对于使用者是非常不友好的,很多人第一次看到这样的一致性保证都是 比较吃惊的。

那么GFS为什么要设计这样的一致性模型那?GFS在架构上选择这样的设计有它自己的设计哲 学。GFS追求的是简单、够用的原则。GFS主要要解决的问题是如何使用廉价的服务器存储海 量的数据,并且达到非常高的吞吐量(GFS非常好的做到了这2点,但不是本文的主题,这里 就不展开了),并且文件系统本身的实现要简单,能够快速的实现出来(GFS的开发者在开发 完GFS之后,很快就去开发BigT able了)。GFS很好的完成了完成了这样的目标。但是留下了 一致性问题,给使用者带来的负担。但是在GFS应用的前期,一致性不是问题,GFS的主要使 用者(BigT able)就是GFS开发者,他们深知应该如何使用GFS,这种不一致在BigT able中被 很好屏蔽掉(采用上面所说的方法),BigT able提供了很好的一致性保证。

但是随着GFS使用的不断深入,GFS简单够用的架构开始带来很多问题,一致性问题仅仅是其 中的一个。主导了GFS很⻓时间的Leader Sean Quinlan在一次采访中,详细说明了在GFS度 过了应用的初期之后,这种简单的架构带来的各种问题[1]。

开源分布式文件系统HDFS,清晰的看到了GFS的一致性模型给使用者带来的不方便,坚定地 摒弃了GFS的一致性模型,提供了很好的一致性保证。HDFS达到了怎样的一致性,这里就不 在展开了,另外再做详细的讨论。

总之,保证一直性对于分布式文件系统,甚至所有的分布式系统都是很重要的。

1.GFS: Evolution on Fast-forward, Sean Quinlan, 2009, 

https://queue.acm.org/detail.cf m?id=1

原文链接:https://mp.weixin.qq.com/s/GuJ6VqZJy3ONaVOWvQT9kg

Image placeholder
WeekTrip
未设置
  50人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Kafka 优秀的架构设计!它的高性能是如何保证的?

应大部分的小伙伴的要求,今天这篇咱们用大白话带你认识Kafka。Kafka 基础消息系统的作用大部分小伙伴应该都清楚,这里用机油装箱举个例子:所以消息系统就是如上图我们所说的仓库,能在中间过程作为缓存

MySQL是怎么保证数据一致性的

在《写数据库同时发mq消息事务一致性的一种解决方案》一文的方案中把分布式事务巧妙转成了数据库事务。我们都知道关系型数据库事务能保证数据一致性,那数据库到底是怎么设计事务这一特性的呢?一、MySQL事务

写数据库同时发mq消息事务一致性的一种解决方案

一、引子《事务注解(@Transactional)引起的数据覆盖故障》一文收到不少反馈。事务里不要有rpc,基本原则,sb封装的太好了,把很多人养傻了,function级别的事务,坑太大。网友一这个是

实践和思考的重要意义(论软件代码设计)

感触 最近这段时间,包括以前,经常听到,程序员们大谈设计模式,这个话题并不陌生,面试必问的问题,活了这么多年,我就一直没搞清楚,为啥面试官喜欢问这个问题。如果一个面试官喜欢问这种问题,我觉得也没啥意思

实践和思考的重要意义(论软件代码设计)

感触最近这段时间,包括以前,经常听到,程序员们大谈设计模式,这个话题并不陌生,面试必问的问题,活了这么多年,我就一直没搞清楚,为啥面试官喜欢问这个问题。如果一个面试官喜欢问这种问题,我觉得也没啥意思。

什么是边缘计算及其重要性?

边缘计算正在改变世界上数百万台设备处理,处理和传递数据的方式。联网设备(IoT)的爆炸性增长,以及需要实时计算能力的新应用,继续推动着边缘计算系统的发展。诸如5G无线之类的更快的联网技术,使边缘计算系

Apache 的架构师们遵循的 30 条设计原则

本文作者叫Srinath,是一位科学家,软件架构师,也是一名在分布式系统上工作的程序员。 他是ApacheAxis2项目的联合创始人,也是ApacheSoftware基金会的成员。 他是WSO2流处理

央视网黄乐:安全合规是一切工作的重要基础

“安全无小事”这一句口号相信大家都较为耳熟,而随着网络技术的不断应用落地,这句口号不再仅仅是一句口号。由于网络安全威胁的不断升级,企业组织对于网络安全的重视程度不断加深。但在安全方面的投入或许还远远不

Laravel 使用 CURD 之外- Domain,大中型 Laravel 项目架构设计

0x01面向领域的Laravel 人类分类思考,我们的代码应该映射这一点 首先说明,我没有提出这个术语『领域』-我从流行的开发模式DDD中学来的。引用牛津词典,『领域』可以描述为『一个特定范围的活

本地读写的多活数据存储架构设计要义

本文由公众号EAWorld翻译发表,转载需注明出处。作者:ParasharBorkotoky 译者:白小白 原文:http://t.cn/AiKO0q4P原题:DesignConsiderations

DTCC2019 :“数据架构设计实践专场”等您来!

  2019年5月8日~5月10日,由IT168旗下ITPUB企业社区平台主办的第十届中国数据库技术大会(DTCC2019),将在北京新云南大酒店召开。本次大会将以“数据风云,十年变迁”为主题,邀请百

如何构建批流一体数据融合平台的一致性语义保证?

一、批流一体架构 批和流是数据融合的两种应用形态 下图来自Flink官网。传统的数据融合通常基于批模式。在批的模式下,我们会通过一些周期性运行的ETLJOB,将数据从关系型数据库、文件存储向下游的目标

一致性哈希算法 PHP 实现

一致性哈希算法(consistenthashing)PHP实现本文转载于 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hotspo

陆天炜: GoldenDB事务一致性处理机制优化历程

前言:GoldenDB是中兴通讯推出的一款自研的金融级交易型分布式数据。针对金融行业关注的数据库事务一致性问题,中兴通讯GoldenDB分布式数据库架构师陆天炜,在DTCC2019数据库大会上做了干货

如何保证缓存与数据库的双写一致性?

分布式缓存是现在很多分布式应用中必不可少的组件,但是用到了分布式缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?CacheAsidePa

数据库大牛李海翔详解全局读一致性技术

作者简介:李海翔,网名“那海蓝蓝”,腾讯金融云数据库技术专家。中国人民大学信息学院工程硕士企业导师。著有《数据库事务处理的艺术:事务管理和并发访问控制》、《数据库查询优化器的艺术:原理解析与SQL性能

阿里面试题:如何保证缓存与数据库的双写一致性?

作者:你是我的海啸出处:https://blog.csdn.net/chang384915878/article/details/86756463只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只

Talos网卡负载优化:基于个性化一致性哈希的负载均衡

本文将详细介绍基于个性化一致性哈希的流量均衡方法。 目录  业务增长带来的流量均衡需求基于一致性哈希的调度策略个性化一致性哈希的负载均衡流量均衡在Talos中的实现前文《小米消息队列的实践》介绍了小米

Redis学习笔记2—缓存、集群、一致性等

缓存淘汰策略为了保证高性能,缓存都保存在内存中,当内存满了之后,需要通过适当的策略淘汰老数据,以便腾出空间存储新数据。数据的淘汰策略,典型的包括FIFO(先进先出,淘汰最老数据),LRU(淘汰最近最少

一键“脱”衣应用DeepNude迅速下线,来看看它涉及的图像修复技术

大数据文摘出品来源:Github发布者:yuanxiaosc上周,又一AI偏门应用DeepNude爆出,一键直接“脱掉”女性的衣服,火爆全球。应用也很容易上手,只需要给它一张照片,即可借助神经网络技术

嗨!你的 2019 晒好封存了吗?快来看程序老兵的 2019 吧!

时间过得真是太快快快了,2019还剩下最后几个小时了。回望即将过去的这一年,老兵哥做了不少事情,有计划内的,也有计划外的,当然还有不少事情没做。赶在最后时刻晒一晒我的2019年,希望从成绩荣誉中获得一

龙头企业应该承担起产业数据生态构建的重任

背景:当前,企业的数据积累和数据处理体系已基本完成,各行业关注的重点转向多渠道、多种数据形式的融合,从局域数据融合转向全域数据融合。海量的数据将在“云端”汇聚,并将实现多种业务之间的高度融合。需求在线

tomcat的重启

点击下方截图可插入当前视频播放画面,了解更多Mackdown语法可以点击上方?图标jsp运行不需要重启tomcat而servlet需要

万字详解Oracle架构、原理、进程,学会世间再无复杂架构

学习是一个循序渐进的过程,从面到点、从宏观到微观,逐步渗透,各个击破,对于Oracle, 怎么样从宏观上来理解呢?先来看一个图,这个图取自于教材,这个图对于从整体上理解ORACLE 的体系结构组件,非

架构转型先行——金融业务场景下的新一代架构实践

  赵勇中国农业银行研发中心架构管理办公室主任工程师中国农业银行研发中心架构管理办公室主任工程师,十年以上金融行业信息化架构设计与管控经验。历经互联网金融、两地三中心、分布式核心银行等大型银行系统工程