天天看点

GFS中一致性的总结

GFS中一致性的总结

1. 一致性(consistency)的背景

只要在分布式系统中,不管是分布式文件系统还是分布式数据库,只要数据存在多个副本,就一定会涉及到一致性的问题。通常来说一致性主要分为内部一致性和外部一致性。

这两个一致性有本质的区别,事务的 ACID 属性中有一个 C 是 Consistency,表示事务的执行一定保证数据库中数据的约束不被破坏。而分布式领域提及的 Consistency 表示系统的正确性模型。

下面只讨论分布式系统中的一致性。

2.影响一致性的操作

  • 修改元数据
  • 写数据:向一个文件块中写数据,客户端指定要写的数据和要写的数据在一个文件中的偏移量。
  • 追加数据:客户端指定要将哪些数据追加到哪个文件后,系统返回追加成功的数据的起始位置。

负载影响:

下面的两种情况只跟写请求有关,读请求不会影响一致性:

  • 无并发情况:假设一个写请求处理完后,下个写请求再来。
  • 并发:GFS同时执行多个写请求到一个文件块上。

3. GFS的一致性

GFS的一致性主要分为两种,一种的元数据的一致性,一种是Chunk的一致性。

主要区分一下:consistent和defined

consistent: 多个replicas之间的数据相同;

defined:每个client最初写入指定offset的数据 和 后来从offset读出的数据相同;

对于元数据:

因为元数据只有一份(不考虑shadow master和日志文件),这就不会存在副本一致性的问题了,只需要考虑传统数据库中内部一致性问题,即符合数据库的内部约束,GFS对元数据的修改需要加锁,隔离各个操作来保证一致性。

  1. 修改元数据(无并发情况):

    此时操作结果只有成功或者失败,元数据永远是一致的。

  2. 修改元数据(并发情况):

    GFS存在锁机制,会将各个操作依次执行,与无并发一样,只不过并发的修改后值是多少不确定(因为并发执行的程序推进顺序是不可知的,但元数据还是一致的。

    可以看到,如果数据只有一份,总是一致的。只有并发会产生语义的问题,需要根据应用逻辑进行并发处理。

对于写入文件:

每一个chunk默认有3个副本,不同的副本存在不同的节点上,master会设置一个primary replica,两个secondary replica。

当写操作和追加操作失败,数据会出现部分被修改的情况,肯定会出现副本不一致的情况,这时就依赖master的重备份机制,将正确的副本重新备份到设定阈值的数量为止。

以下只考虑操作成功的情况:

  1. 写一个chunk(无并发情况):

    写一个chunk时,当client获得了handle以及primary的位置后,会向其发起写请求,primary来确定写操作的执行顺序,因为没有并发,所以直接执行该请求,然后primary命令其他secondary执行该请求,其他secondary都按照这个顺序执行写操作,保证了全局有序性,并且当所有的replica都写入成功时,primary才会返回succes,这样用系统延迟保证了数据的强一致性(consistent),即所有的副本值均相同。

    这个强一致指每个写操作成功后,所有客户端都能看到这个修改。即论文中说的 defined 。defined 的意思是知道这个文件是谁写的(那么谁知道呢?肯定是自己知道,其他客户端看不到文件的创建者)。也就是当前客户端在写完之后,再读数据,肯定能读到刚才自己写的。

  2. 写一个chunk(并发情况):

    这个时候primary可能同时会收到多个client对同一个chunk的写请求,例如:

    假设有两个client,client1想把这个chunk写为data1,client2想把这个chunk写为data2,此时primary需要将这些写操作按照某个机制排序,比如,write_data2, write_data1。

    然后在primary1本地执行,于是这个chunk会被先写成data2,然后被覆盖成data1。

    之后所有secondary副本都会按照这个顺序来执行操作,于是所有副本都是data1,这时副本是满足consistent的,因为所有操作都正确执行了,所以两个client都收到写成功了。但是谁也不能保证数据一定是自己刚才写的,也就是 undefined 。这与最终一致性有点像(系统保证所有副本最终都一样,但是不保证是什么值)。

对于追加文件:

追加数据时,会追加到该chunk的末尾,其实和写一个chunk+无并发基本一样。

但由于追加操作和写文件不一样,追加操作不是幂等的,当一次追加操作没有成功,客户端重试时,不同副本可能被追加了不同次数,这也就是

at least one

特性。

1. 追加数据(无并发)

假设有以下情况:

client追加了一个数据a,第一次执行一半失败了,那么现在这个chunk所有的副本情况如下:

  • primary: 原始数据, offset1:a
  • secondary1: 原始数据, offset1:a
  • secondary2: 原始数据

于是,client重新发送追加写请求,,因为primary会先执行操作再将请求发给secondary,所以primary当前文件是最长的(先不考虑primary改变的情况)。primary继续往offset2(当前文件末尾)追加,并通知所有secondary往offset2追加,但是secondary2的offset2不是末尾,所以会先补空。如果这次追加操作成功,数据最终会是这样:

  • primary:原始数据,offset1:a,offset2:a
  • secondary1:原始数据,offset1:a,offset2:a
  • secondary2:原始数据,offset1:*,offset2:a

并且给客户端返回 offset2 。

于是数据中间一部分是 inconsistent。但是对于追加的数据是 defined 。客户端再读offset2,可以确定读到a。

这就是追加操作的

defined interspersed with inconsistent

2. 追加数据(并发)

两个client分别向同一个文件追加数据a和数据b

  • client1: 追加a
  • client2: 追加b

primary接收到追加操作后,进行序列化,假设如下:

  • primary: b,a

然后执行操作,假设b执行的一般的时候失败了,那么client2再发送一次追加b,priamry重新追加一遍,那么数据情况如下:

  • primary: 原始数据,off1:b,off2:a,off3:b
  • secondary1:原始数据,off1:b,off2:a,off3:b
  • secondary2:原始数据,off1: ,off2:a,off3:b

client1收到GFS返回的off2(表示a追加到了文件的off2位置),client2收到off3

也满足off2和off3是 defined ,off1是 inconsistent ,所以总体来说是

defined interspersed with inconsistent

可以看到,不管有没有并发,追加数据都不能保证数据全部 defined,只能保证有 defined ,但是可能会与 inconsistent 相互交叉。

继续阅读