Megastore: Providing Scalable, Highly Available Storage for Interactive Services

ABSTRACT

满足交互式在线服务的需求的存储系统。
既有NoSQL的伸缩性,又有传统RDBMS的便利。
在细粒度的数据分区中提供完全可序列化的 ACID 语义。这种分区允许我们以合理的延迟在广域网中同步复制每个写入,并支持数据中心之间的无缝failover。

1. INTRODUCTION

面临的挑战:

  1. 高度可扩展性:互联网带来了大量的潜在用户。
  2. 快速发布上线:为了争夺用户。
  3. 低延迟:快速响应。
  4. 一致的数据视图:更新需要立即且持久可见。
  5. 高可用:7*24提供服务

Bigtable已经支持的可扩展和高可用。那么问题是:
问题1:BigTable为什么不能支持快速发布上线?Megastore是怎么支持的?
论文里没有找到相关描述。

问题2:Megastore是怎么实现多数据中心低延迟的?
对于读,通过一个协调器维护本地实体组的状态,如果本地实体组状态最新,则每次都从本地实体组读取数据,延迟平均为数十毫秒。
对于写,通过一阶段的Paxos算法和批量写,延迟差不多上百毫秒。

问题3:Megastore如何实现一致的数据视图的?
通过current级别的读,以及Paxos算法写实现多副本之间同步复制。

设计理念:
对数据存储进行分区,每个分区独立复制,并且提供完全的ACID语义,但是分区之间只提供有限的一致性保证。
提供了传统关系数据的功能,比如二级索引,但仅提供那些可以在用户可容忍的延迟限制内扩展的功能,并且只有我们的分区方案可以支持的语义。

2. TOWARD AVAILABILITY AND SCALE

可用性:我们实现了针对长距离链路优化的同步、容错日志复制器。
可扩展:为了实现规模化,将数据划分为大量小型数据库,每个数据库都有自己的复制日志,存储在每个副本的 NoSQL 数据存储中。

2.1 Replication

提供广阔物理区域内的数据复制。

2.1.1 Strategies

异步主从复制(Asynchronous Master/Slave)
两个问题:

  1. 从节点可能丢数据
  2. 无法满足整体系统的可线性化

同步主从复制(Synchronous Master/Slave)

乐观复制(Optimistic Replication)
数据可以在任何一个副本中写入,然后这个改动会异步地同步到其他副本中。因为可以在任何一个副本就近写入,所以系统的可用性很好,延时也很低。
但是,什么时候会同步完成我们并不知道,所以系统只能是最终一致(eventually-consistent)的。
而且,这个系统基本无法实现事务,因为两个并发写入究竟谁先谁后很难判定,所以隔离性就无从谈起了,而“可线性化”自然也就没法做到了。

2.1.2 Enter Paxos

尽管最终一致的系统具有操作优势,但目前在快速应用程序开发中要放弃 read-modify-write 习惯用法太难了。

问题4:为什么最终一致性的系统,需要放弃read-modify-write的习惯用法?
如果只是modify-write,那么可以保证最终一致性。但是如果是read-modify-write的用法,如果不支持事务,read的时候读取的是过时的值,则可能导致数据错误。

即使Paxos算法有容错性,如果使用一个日志,也会有如下问题:
由于副本分布在很广的区域,通信延迟限制了整体的吞吐量。
当没有副本或大多数副本未能确认写入时,进度会被阻塞。
所以为了提高可用性和吞吐量,使用了多个日志,每个日志管理自己的分区。

2.2 Partitioning and Locality

2.2.1 Entity Groups

为了扩展吞吐,减少本地化中断,将数据划分为实体组集合。
单个实体组在数据中心之间通过单阶段的Paxos算法同步复制。支持ACID事务。实体组数据以及复制元数据都存储在可扩展的NoSQL数据存储中。
实体组之间要么使用昂贵的两阶段事务,要么采用Megastore提供的基于队列的异步消息机制。

Figure1: Scalable Replication

Figure2: Operations Across Entity Groups

2.2.2 Selecting Entity Group Boundaries

划分实体组,太大会因为写入量大降低吞吐,太小了会增加跨实体组间的操作。
Email:每个账号是一个实体组。
Blog:每个用户的配置文件是一个实体组。但是,博客是协作的,没有单一的永久所有者。我们创建第二类实体组来保存每个博客的帖子和元数据。博客声明的唯一名称是一个实体组。当单个用户操作同时影响博客和个人资料时,该应用程序依赖于异步消息传递。对于像创建新博客并声明其唯一名称这样的低流量操作,两阶段提交更方便且性能充分。
Maps: 把地球划分成不重叠的片,每个片是一个实体组。更新的时候采用两阶段提交保证原子性。

2.2.3 Physical Layout

使用Bigtable进行底层存储。

3. A TOUR OF MEGASTORE

3.1 API Design Philosophy

为什么Megastore不像标准的关系模型那样依赖查询时join来服务用户操作:

  • 大容量交互式负载需要有可预测的性能。
  • 目标应用读多写少,需要把读时间转移到写时间。
  • Bigtable等键值存储中存储和查询分层数据非常简单。

正常都是针对特定表的scan或lookup。join的支持都是通过代码实现。
对于join,多个查询返回同一个表相同顺序的key,merge阶段返回key的交集。
对于outer join,对于初始查询结果,进行并行索引的lookup。

3.2 Data Model


CREATE SCHEMA PhotoApp

CREATE TABLE User {
  required int64 user_id;
  required string name;
} PRIMARY KEY(user_id), ENTITY GROUP ROOT;

CREATE TABLE Photo {
  required int64 user_id;
  required int32 photo_id;
  required int64 time;
  required string full_url;
  optional string thumbnail_url;
  repeated string tag;
} PRIMARY KEY(user_id, photo_id),
  IN TABLE user,
  ENTITY GROUP KEY(user_id) REFERENCES User;
  
CREATE LOCAL INDEX PhotosByTime
  ON Photo(user_id, time);

CREATE GLOBAL INDEX PhotosByTag
  ON Photo(tag) STORING (thumbnail_url);

首先是定义了一个叫做 PhotoApp 的 Schema,可以认为是定义了数据库里的一个库(database)。然后定义了一张叫做 User 的表,并且定义其中的 user_id 是主键,并且定义了这个表是实体组(Entity Group)的一个根(Root)。一条 User 表的记录,就代表了一个用户。
接着,定义了一张叫做 Photo 的表,其中的主键是 user_id 和 photo_id 两个字段的组合。并且,这张表是关联到前面的 User 表这个根上的。这个挂载,是通过 user_id 这个字段关联起来的。这个关联关系,就是我们上一讲所说的“挂载”。实际上,我们可以有多个表,都关联到 User 表这个根上。而所谓的实体组,在逻辑上就是一张根表 A,并且其他表可以通过外键,关联到根表 A 的主键上。并且,这个关联是可以层层深入的。比如我们还可以再定义一个表,叫做 PhotoMeta,里面可以再通过 user_id 和 photo_id,再关联到 Photo 表上。
最后,Schema 里分别建立了两个索引:

一个是叫做 PhotosByTime 的本地索引(Local Index),索引的是 Photo 表里 user_id 和 time 字段的组合;
另一个,是叫做 PhotosByTag 的全局索引(Global Index),索引的是 Photo 表里的 Tag 这个字段,并且它专门设置了一个特定的 STORING 参数,指向了 Photo 表里的 thumbnaill 这个字段。

Figure4: Sample Data Layout in Bigtable

3.2.1 Pre-Joining with Keys

基于Bigtable中数据按照rowKey顺序排列的特性,根表和其他数据表是按照rowKey顺序存储的。实体组内所有表的列组成Bigtable宽表的列。只不过每一行数据中只有自己表的列有值。
模式声明键按升序或降序排序,或者完全避免排序:SCATTER 属性指示 Megastore 为每个键添加一个两字节的散列。以这种方式对单调递增的键进行编码可以防止跨 Bigtable

3.2.2 Indexes

分为本地索引和全局索引。
本地索引被视为每个实体组的单独索引。 它用于在实体组中查找数据。 PhotosByTime 是本地索引的一个示例。 索引条目存储在实体组中,并与主实体数据一致地自动更新。
全局索引跨越实体组。 它用于在事先不知道包含它们的实体组的情况下查找实体。PhotosByTag 索引是全局的,可以查找标有给定标签的照片,而不管所有者是谁。 全局索引扫描可以读取许多实体组拥有的数据,但不能保证反映所有最近的更新。异步更新。

索引的优化

3.2.2.1 Storing Clause.

覆盖索引。可以通过一个 STORING 语句,指定索引里存储下对应的数据记录的某一个字段的值。这样,我们的查询只需要检索索引,就能拿到需要字段的值。不需要进行二次请求。

3.2.2.2 Repeated Indexes

支持为repeated类型的字段建立索引。
Megastore 会为里面的每一个 tag 都记录一条索引。这样,我们就可以通过索引,反向查询到某一个 tag 关联到的所有的 Photo 记录。Megastore 这种支持 repeated 字段的索引,使得我们不需要为这样的单个 repeated 字段,去单独建立一张子表。无论这张子表是一张独立的表,还是像 Megastore 的实体组一样挂载在 Root 表上,都很浪费存储空间,也让这个数据表结构变得过于复杂,不容易理解。

3.2.2.3 Inline Indexes

可以把 Schema 中定义的本地索引,PhotosByTime 这个原本索引 Photo 实体的索引,变成 User 实体的内联索引。这样,User 表实际上会相当于多了一个 repeated 的虚拟字段(virtual)。而既然是 repeated 字段,那它其实就是一个 List。List 里的每一个结构体,都存放了两个信息,一个是 PhotosByTime 里面的 time 信息,另一个是对应的这个 time,对应的是 Photos 里的哪一条记录。
这样,通过在父实体里添加了一个虚拟字段,我们对于子实体里的数据查询,直接在父实体里就能够完成了,而不需要再去查询具体的索引数据。因为在应用开发的时候,比如在 Instagram 里,我们看一个用户最近的照片,都是先取到 User 这样的父实体,再根据 user_id 和索引去查询它的照片信息。当有了内联索引之后,我们在第二步查询子实体数据的时候,就可以少一次索引的访问了。

Mapping to Bigtable

igtable 列名是 Megastore 表名和属性名的串联,允许来自不同 Megastore 表的实体映射到同一个 Bigtable 行而不会发生冲突。
每一条索引,都是作为一行数据,存储在 Bigtable 里的。

CREATE LOCAL INDEX PhotosByTime ON Photo(user_id, time);

PhotosByTime 这个索引由 user_id 和 time 这两个字段组成;并且它索引的是 Photo 这个表,对应的 Photo 表的主键就是 user_id 和 photo_id;那么,索引这一行的行键,就是 ((user_id, time), (user_id, photo_id)) 这样的一个组合;而如果我们的索引,指向的是一个 repeated 的字段,比如 tags,那么每一个 tag 都会有一行数据。比如有三个 tag,分别是 [tag1, tag2, tag3],我们的索引,就会有三条记录,分别是 (tag1, (user_id, time)), (tag2, (user_id, time)) 和 (tag3, (user_id, time))。

3.3 Transactions and Concurrency Control

基于Bigtable的版本实现MVCC。
Megastore 的事务系统对于读取数据提供了 current,snapshot 以及 inconsistent 三种模式。
current:在读数据之前,Megastore 的事务系统,会先确认已经提交(commit)的事务都已经应用(apply)成功。
snapshot:会等待当前是否有已经提交的事务应用完成,而是直接返回上一个完全应用的事务对应的数据版本。
inconsistent:完全忽视事务系统的日志信息,直接获取到 Bigtable 里面最新的数据。

问题5:使用snapshot会有什么问题?
snapshot读的是已经commit的数据,也就是说已经通过了并发竞争,但是可能还没有写入到Bigtable的数据。

问题6:current和snapshot和inconsistent的区别是什么?
答:current和snapshot都是支持事务的,不管在哪儿读都是一致的,可线性化的,只不过读取的版本不同,current通过等待正在执行的事务完成,获取最新版本;snapshot不等待,读上一个已经完成的版本。而inconsistent不支持事务,也就是不借助版本信息,直接读取最新数据,有可能不满足可线性化。

提交事务通过下面5个步骤:

  1. 读(Read):我们先要获取到时间戳,以及最后一次提交的事务的日志的位置。此处都是current read。
  2. 应用层的逻辑(Application Logic):我们要从 Bigtable 读取数据,并且把所有需要的写操作,收集到一条日志记录(log entry)中。
  3. 提交事务(Commit):通过 Paxos 算法,我们要和其他数据中心对应的副本,达成一致,把这个日志记录追加到日志的最后。
  4. 应用事务(Apply):也就是把实际对于实体和索引的修改,写入到 Bigtable 里。
  5. 清理工作(Clean UP):也就是把不需要的数据删除掉。
3.3.1 Queues

队列提供实体组之间的事务性消息传递。异步执行事务。

3.3.2 Two-Phase Commit

实体组之间通过两阶段提交同步执行事务。会有很高的latency,并且增加竞争风险。

3.4 Other Features

我们与 Bigtable 的全文索引建立了紧密的集成,其中更新和搜索参与了 Megastore 的事务和多版本并发。在 Megastore 模式中声明的全文索引可以索引表的文本或其他应用程序生成的属性。
同步复制足以防御最常见的损坏和事故,但在程序员或操作员错误的情况下,备份可能非常宝贵。 Megastore 的集成备份系统支持定期完整快照以及事务日志的增量备份。恢复过程可以将实体组的状​​态恢复到任何时间点,可选择省略选定的日志条目(如意外删除后)。备份系统符合删除数据过期的法律和常识原则。
应用程序可以选择加密静态数据,包括事务日志。加密对每个实体组使用不同的密钥。我们避免授予相同的操作员访问加密密钥和加密数据的权限。

4. REPLICATION

讨论同步复制的核心:低延迟的Paxos实现。

4.1 Overview

  • 写或读可以从任意副本发起。
  • 为数据提供ACID语义。
  • 通过将组的事务日志同步复制到副本的法定人数来完成每个实体组的复制。
  • 写需要一次数据中心内的通信。
  • 正常读在本地执行。current级别的读,可以提供如下保证:
    • 读取始终能观察到最后确认的写入。
    • 在观察到写入之后,所有未来的读取都会观察到该写入。

4.2 Brief Summary of Paxos

Paxos 算法是一种在一组副本之间就单个值达成共识的方法。
它可以允许2F+1个副本中有F个副本故障。一旦大多数人选择了一个值,以后所有读取或写入该值的尝试都将达到相同的结果。
数据库通常使用 Paxos 来复制事务日志,其中一个单独的 Paxos 实例用于日志中的每个位置。新值将写入日志中最后选择的位置之后的位置。
写入至少需要一次prepares和accepts。
读取至少需要一次prepares。

4.3 Master-Based Approaches

为了减少延迟,很多系统使用一个master来处理所有的读写。
好处:

  • master处理所有的写入,所有状态是最新的。可以在没有任何通信的情况下提供一致的读结果。
  • 可以在accept中带上下一次的prepare,是的两次通信减少为一次通信。
  • master还可以批量写。

坏处:

  • 事务处理必须在主副本附近进行,以避免因顺序读取而累积延迟。
  • master必须有足够的资源处理所有的请求。并且master副本在成为master之前一直是资源浪费状态。
  • master的failover需要很复杂的状态机,可能需要耗费很长时间,很难避免用户可见的中断。

4.4 Megastore’s Approach

4.4.1 Fast Reads

正常情况在本地副本读取。
Megastore 在每个数据中心,都引入了一个叫做协同服务器(Coordinator Server)的节点,这个节点是用来追踪一个当前数据中心的副本里,已经观察到的最新的实体组的集合。对于所有在这个集合里的实体组,我们只需要从本地读数据就好了。如果实体组不在这个集合里,我们就需要有一个“追赶共识”(catch up)的过程。

4.4.2 Fast Writes

为了实现快速的单次往返写入,Megastore 采用了基于 master 的方法使用的预准备优化。 在基于 master 的系统中,每个成功的写入都包含一个隐含的准备消息,授予 master 为下一个日志位置发出接受消息的权利。 如果写入成功,则准备好,下一次写入直接跳到接受阶段。 Megastore 不使用专门的master,而是使用leader。
我们为每个日志位置运行 Paxos 算法的独立实例。 每个日志位置的领导者是与前一个日志位置的共识值一起选择的杰出副本。 领导者仲裁哪个值可以使用提议编号为零。 第一个向领导者提交值的写入者有权要求所有副本接受该值作为提案编号为零。 所有其他编写者都必须依靠两阶段 Paxos。
由于写入者必须在将值提交给其他副本之前与领导者进行通信,因此我们最大限度地减少了写入者-领导者的延迟。 我们根据大多数应用程序从同一区域重复提交写入的观察来设计选择下一个写入领导者的策略。 这导致了一个简单但有效的启发式方法:使用最近的副本。

4.4.3 Replica Types

为了进一步提升性能和服务器使用的效率,Megastore 对于每一个数据中心的副本,还分成了三种不同的类型:
第一种叫做完全副本(Full Replica),也就是拥有前面我们说的所有的这些服务。
第二种叫做见证者副本(Witness Replica),这样的副本只参与投票,并且记录事务日志。但是它不会保留实际的数据库数据,所以我们也不能从这些副本查询数据。如果我们的完整副本比较少,比如只有北京和上海两个数据中心,这样我们无法完成 Paxos 需要的至少三个节点的投票机制。那么,我们就可以随便在哪个数据中心,再增加一个见证者副本,确保可以完成“多数通过”的投票机制。
第三种叫做只读副本(Read-Only Replica),它恰恰和见证者副本相反,它不会参与投票,但是会包含完整的数据库数据。读取这个副本,可以拿到特定时间点的数据快照,我们可以把它当作是一个异步的数据备份。在明确知道我们读取数据的时候,不需要保障“可线性化”的时候,也可以直接去读取这个数据。因为大部分的数据库都是读多写少的,这个副本也可以减轻我们的完全副本的负载。

4.5 Architecture

Figure 5: Megastore Architecture Example

每一个数据中心里,都有我们的应用服务器,应用服务器本身会通过一个 Megastore 的库,来完成和 Megastore 的所有交互操作。所有的外部请求,比如用户数据更新、上传照片,都是先到应用服务器,再由应用服务器调用它所包含的 Megastore 的库,来进行数据库操作。
每一个数据中心里的数据,都是存储在底层的 Bigtable 里的。无论是事务日志,还是实际的数据,都是通过 Bigtable 存储。所以我们也就不用操心数据存储层面的灾备、容错、监控等一系列问题了。
中间层,Megastore 有两类服务器。一类是我们刚刚说过的协同服务器,用来维护本地数据是否是最新版本。另一类,叫做复制服务器(Replication Server)。我们所有的数据写入请求,如果是写到本地数据中心里的,直接写 Bigtable。如果是要告诉远端的另外一个数据中心,则是发送给那个数据中心的复制服务器。这个复制服务器,本质上就是起到一个代理的作用。此外,复制服务器还要负责定期扫描本地的副本里,因为网络故障、硬件故障,导致没有完整写入或者应用的数据库事务,然后通过 Paxos 里的 no-op 操作去同步到最新的完整数据。

4.6 Data Structures and Algorithms

4.6.1 Replicated Logs

每个副本存储组已知的日志条目的突变和元数据。 为了确保副本即使从先前的中断中恢复也可以参与写入仲裁,我们允许副本接受无序提案。 我们将日志条目作为独立单元存储在 Bigtable 中。

16439696999297

4.6.2 Reads

Figure 7: Timeline for reads with local replica A

  1. 查询本地的协同服务器,看看想要查询的实体组是否是最新的。
  2. 根据查询的结果,来判断是从本地副本还是其他数据中心的副本,找到最新的事务日志位置。所谓的日志位置,你可以认为是一个自增的事务日志的 ID。因为我们的事务日志,并不像单机的数据库那样,写到文件系统里,而是写到 Bigtable 的一张表里的。因为 Bigtable 支持单行事务,所以事务日志作为一行数据写到 Bigtable 也是一个原子提交。而 Bigtable 的数据存储又是按照行键连续存储的,也非常适合事务日志的这种追加写的特性:
    1. 如果协同服务器告诉我们,本地的实体组就是最新的。那么,我们就从本地副本,拿到最新的日志位置,以及时间戳就好了。在实践当中,Megastore 不会等待查询到本地是不是最新版本,再来启动这个查询。而是会在查询协同服务器的同时就从 BigTable 里面获取这个事务日志的数据。这样通过并行查询的方式来缩短网络延时,即使本地不是最新版本,也无非浪费一次 Bigtable 的数据读取而已。
    2. 如果本地副本不是最新的,那么我们就要用到 Paxos 的投票特性了,我们会向 Paxos 其他的节点发起请求,让它们告诉我们最新的事务日志位置是什么。根据多数意见,我们就知道此时最新的事务日志的位置了。然后,我们可以挑一个响应最及时的,或者拥有最新更新的副本,从它那里来开始“追赶共识”。因为我们的本地节点往往是响应最快的,所以从本地副本去“追赶共识”,往往也会是一种常用策略,但这不是我们的必然选择。
  3. 就是“追赶共识”的过程了。一旦从哪个副本启动“追赶共识”的过程确定了,我们就只要这样操作就好了:
    1. 我们先去看,这个副本是否存在某些“空洞”。也就是,根据最新的日志位置,具体有哪些日志位置上,我们没有一个已经知道的值。如果有的话,我们就使用 Paxos 算法中的 no-op 操作,来确定这个日志位置的值,也就是日志内容。在对所有的这些“空洞”都确认了这个值之后,我们的事务日志就补齐了。你可能要问,如果这个副本是知道最新事务日志的位置的话,它也会有空洞么?答案是有,因为我们的这个节点,可能因为网络中断,没有拿到中间一段的事务日志。而在网络恢复参与投票之后,根据 Paxos 算法它可以知道最新的日志位置。
    2. 然后我们在这个副本的 Bigtable 里,顺序应用这些事务日志,就能让这个副本的Bigtable 追赶上整个分布式系统的“共识”了。
    3. 如果在这个追赶共识的过程里失败了,我们就换一个其他的副本来尝试启动这个追赶过程。
  4. 如果我们在“追赶”的过程中,是通过本地副本来发起整个追赶过程的。那么一旦这个追赶的过程完成,意味着本地的数据已经更新到了最新的状态。那么它会向本地的协同服务器发起一个 Validate 的消息,让协同服务器知道这个实体组的数据已经是最新的了。这个消息不需要等待协同服务器确认返回,因为即使它失败了,无非是下一次读数据的时候,把前面的整个过程再走一遍就是了。
  5. 根据使用了哪一个“副本”来“追赶共识”,通过拿到的日志位置和时间戳,去问它的 Bigtable 要数据。如果这个时候,这个副本不可用了,那么就要再找一个副本,从前面的第三步“追赶共识”开始重复一遍。
4.6.3 Writes

Figure 8: Timeline for writes

  1. Accept Leader :客户端会先尝试直接向 Leader 副本发起一个 Accept 请求。并且这个请求,会设定为 Paxos 算法中的第 0 轮的 Accept 请求。如果这个请求被 Leader 接受了,我们就会跳转到后面的第 3 步,向所有的副本都发起 Accept 请求来达成共识。
  2. Prepare:如果第 1 步的请求失败了,那么我们就正常走一个 Paxos 算法的流程,向所有的副本,发起一个 Prepare 请求。Prepare 请求里面带的提案编号,就是正常 Paxos 算法的提案编号,从 1 开始,用当前客户端见过的最大的编号 +1。
  3. Accept :也就是让所有副本都去接收客户端发起的提案。如果没有获得多数通过而失败了,那么我们就重新回到第 2 步,重新走 Prepare-Accept 的过程。
  4. Invalidate:在 Accept 阶段成功之后,我们需要向所有没有 Accept 最新的值的副本,发起一个 Invalidate 的请求,确保它们的协同者服务器,把对应的实体组从 Validate 的集合中去除掉了。
  5. Apply:在 Apply 阶段,客户端会让尽可能多的副本,去把实际修改应用到数据库里。如果发现要应用的数据和实际提案的数据不同,会返回一个冲突的报错(conflict error)。

注:因为在上一次的accept中已经带了prepare请求,已经拿到了最新的log位置,所以第一个来的请求可以直接用最新位置,即跳过prepare直接accept,但是后面再来的写请求,就需要走两阶段提交。

4.7 Coordinator Availability

协调器进程在每个数据中心运行,并仅保留有关其本地副本的状态。 在上面的写算法中,每个完整副本必须要么接受要么让其协调器失效,因此看起来任何单个副本故障(Bigtable 和协调器)都会导致不可用。
在实践中,这不是一个常见的问题。 协调器是一个简单的进程,没有外部依赖,也没有持久存储,因此它往往比 Bigtable 服务器稳定得多。 尽管如此,网络和主机故障仍然会使协调器不可用。

用到协同服务器的总结:

  • 读时,是查询本地的协同服务器,看看想要查询的实体组是否是最新的。如果是最新就本地读,如果不是最新,进行追赶共识。
  • 追赶共识成功后,会想协同服务器发送一个Validate消息,让协同服务器知道这个实体组的数据已经是最新的了。
4.7.1 Failure Detection

我们使用谷歌的 Chubby 锁服务:协调器在启动时在远程数据中心获取特定的 Chubby 锁。为了处理请求,协调器必须持有其大部分锁。如果它从崩溃或网络分区中丢失了大部分锁,它会将其状态恢复为保守的默认值,考虑到其权限内的所有实体组都已过时。副本的后续读取必须从大多数副本查询日志位置,直到重新获得锁并重新验证其协调器条目。
如果数据中心的协调器突然变得不可用,则所有writers都得等着Chubby的锁过期才能完成写入。这段时间可能会有10秒的中断。但是当协调器failover之后恢复状态的时候,不会阻塞读写,因为这个时候可以退回Paxos的两阶段进行数据读写。
如果协调器获取这Chubby的锁,但是跟其他提案者失去联系,那么这个实体组的所有写入都会中断。这种情况需要手动禁用隔离的协调器。只遇到几次这种情况。

总之,因为每次读写都需要读取协调器,如果协调器挂了,就得等着。所以在Megastore中,协调器是一个单点。好在协同服务器的这些数据,都只维护在内存里,而且只需要维护本地数据中心的实体组是否是最新的这样一个状态。并且它也没有任何外部依赖,即使节点故障,重新启动一个新的节点,里面的数据也可以通过 Paxos 的“多数投票”过程,恢复出来。因为足够简单,所以协同服务器会比 Bigtable 更加稳定。

问题7:为什么协调器挂了就得等着,不等Chubby锁租约到期,设置一个超时时间不可以么?
目前通过论文了解的信息,如果不等着,可能会减少一个有效的投票者。并且读不能读取本地副本,写退化为Paxos的两阶段提交,代价较大。

4.7.2 Validation Races

除了可用性问题之外,协调器的读写协议还必须应对各种竞争条件。无效消息总是安全的,但必须小心处理验证消息。通过始终发送与操作关联的日志位置,在协调器中保护早期写入的验证和后期写入的无效之间的竞争。编号较高的无效总是胜过编号较低的验证。还存在与在位置 n 的写入器无效和在某个位置 m < n 的验证之间的崩溃相关的竞争。我们为协调器的每个化身使用唯一的 epoch 编号来检测崩溃:仅当自最近一次读取协调器以来 epoch 保持不变时,才允许验证修改协调器状态。
总之,就可用性的影响而言,使用协调器允许从任何数据中心进行快速本地读取并不是免费的。但在实践中,运行协调器的大多数问题都可以通过以下因素得到缓解:

  • 协调器是比 Bigtable 服务器简单得多的过程,具有更少的依赖关系,因此自然更可用。
  • 协调器简单、同质的工作量使其成本低廉且可预测。
  • 协调器的少量网络流量允许使用具有高可靠连接的网络 QoS。
  • 操作员可以在维护或不健康期间集中禁用协调器。
  • Chubby 锁的法定人数可以检测到大多数网络分区和节点不可用。

4.8 Write Throughput

我们对 Paxos 的实现在系统行为方面进行了有趣的权衡。多个数据中心中的应用程序服务器可能会同时启动对同一实体组和日志位置的写入。除了其中一个之外,所有这些都将失败并需要重试他们的事务。同步复制增加的延迟增加了给定每个实体组提交率发生冲突的可能性。
将该速率限制为每个实体组每秒几次写入会产生微不足道的冲突率。对于其实体一次由少数用户操作的应用程序,此限制通常不是问题。我们的大多数目标客户通过更精细地分片实体组或确保将副本放置在同一区域中来扩展写入吞吐量,从而降低延迟和冲突率。
具有一些服务器“粘性”的应用程序可以很好地将用户操作批量处理为更少的 Megastore 事务。 Megastore 队列消息的批量处理是一种常见的批处理技术,可降低冲突率并提高聚合吞吐量。
对于必须定期超过每秒写入数的组,应用程序可以使用协调服务器分配的细粒度咨询锁。对事务进行背靠背排序避免了与重试相关的延迟以及在检测到冲突时恢复到两阶段 Paxos。

4.9 Operational Issues

当特定的完整副本变得不可靠或失去连接时,Megastore 的性能可能会下降。我们有多种方法来应对这些故障,包括:将用户从有问题的副本中路由出去、禁用其协调器或完全禁用它。在实践中,我们依赖于多种技术的组合,每种技术都有其自身的权衡。
对中断的第一个也是最重要的响应是通过将流量重新路由到其他副本附近的应用程序服务器来禁用受影响副本上的 Megastore 客户端。这些客户端通常会遇到影响其下方存储堆栈的相同中断,并且可能无法从外部世界访问。
如果不健康的协调器服务器可能继续持有它们的 Chubby 锁,那么仅重新路由流量是不够的。下一个响应是禁用副本的协调器,确保问题对写入延迟的影响最小。 (第 4.7 节更详细地描述了这个过程。)一旦写入者免除了使副本的协调器无效的问题,不健康的副本对写入延迟的影响是有限的。只有写入算法中最初的“接受领导者”步骤依赖于副本,并且我们在退回到两阶段 Paxos 并为下一次写入提名一个更健康的领导者之前保持紧迫的截止日期。
一个更严厉且很少使用的操作是完全禁用副本:客户端和复制服务器都不会尝试与其通信。虽然隔离副本看起来很吸引人,但主要影响是可用性受到影响:少了一个有资格帮助编写者形成法定人数的副本。有效的用例是尝试的操作可能会造成伤害——例如当底层 Bigtable 严重过载时。

4.10 Production Metrics

Megastore 已在 Google 内部部署多年;超过 100 个生产应用程序将其用作存储服务。在本节中,我们报告了一些对其规模、可用性和性能的测量。
图 9 显示了可用性分布,以每个应用程序、每个操作为基础进行测量。尽管机器故障、网络故障、数据中心中断和其他故障源源不断,但我们的大多数客户都看到了极高水平的可用性(至少五个 9)。我们样本的底端包括一些仍在测试中的预生产应用程序和具有更高故障容限的批处理应用程序。
平均读取延迟为数十毫秒,具体取决于数据量,表明大多数读取是本地的。
大多数用户看到的平均写入延迟为 100–400 毫秒,具体取决于数据中心之间的距离、正在写入的数据大小以及完整副本的数量。图 10 显示了读取和提交操作的平均延迟分布。

Figure 9: Distribution of Availability

Figure 10: Distribution of Average Latencies

5. EXPERIENCE

对可测试性的高度重视有助于系统的开发。该代码配备了大量(但便宜)的断言和日志记录,并具有全面的单元测试覆盖率。但最有效的错误发现工具是我们的网络模拟器:伪随机测试框架。它能够探索模拟节点或线程之间所有可能的通信顺序和延迟的空间,并在给定相同种子的情况下确定性地再现相同的行为。通过发现触发断言失败(或错误结果)的有问题的事件序列来暴露错误,通常有足够的日志和跟踪信息来诊断问题,然后将其添加到单元测试套件中。虽然对调度状态空间进行详尽的搜索是不可能的,但伪随机模拟通过其他方式探索了比实际更多的东西。通过每晚运行数千小时的模拟运行,测试发现了许多令人惊讶的问题。
在实际部署中,我们观察到了预期的性能:我们的复制协议优化确实在大多数情况下提供本地读取,而写入的开销大约为单个 WAN 往返的开销。大多数应用程序发现延迟是可以容忍的。一些应用程序旨在向用户隐藏写入延迟,并且一些应用程序必须仔细选择实体组边界以最大化其写入吞吐量。这种努力产生了主要的运营优势:Megastore 的延迟尾部明显短于底层的延迟,大多数应用程序可以承受计划内和计划外的中断而很少或不需要人工干预。
大多数应用程序使用 Megastore 模式语言对其数据进行建模。有些人已经在 Megastore 模式语言中实现了他们自己的实体-属性-值模型,然后使用他们自己的应用程序逻辑来建模他们的数据(最著名的是 Google App Engine [8])。有些人使用这两种方法的混合。将动态模式建立在静态模式之上,而不是相反,允许大多数应用程序享受静态模式的性能、可用​​性和完整性优势,同时仍然为需要的人提供动态模式的选项它。
术语“高可用性”通常表示能够掩盖故障以使系统集合比单个系统更可靠。虽然容错是一个非常理想的目标,但它也有自己的缺陷:它经常隐藏持续存在的潜在问题。我们在群里有一句话:“容错就是容错”。很多时候,我们系统的弹性加上对跟踪潜在故障的警惕性不足会导致意想不到的问题:在持续的未纠正问题之上的小瞬时错误会导致更大的问题。
另一个问题是流量控制。容忍有缺陷的参与者的算法可以忽略慢的参与者。理想情况下,不同机器的集合只会与能力最差的成员一样快。如果缓慢被解释为故障并被容忍,那么最快的大多数机器将按照自己的速度处理请求,只有在被努力追赶的落后者的负载减慢时才能达到平衡。我们称这种异常为帮派链节流,让人联想到一群逃犯的形象,他们的前进速度最快是他们能拖动的落后者的速度。
Megastore 的预写日志的一个好处是易于集成外部系统。任何幂等操作都可以作为应用日志条目的一个步骤。
为更复杂的查询实现良好的性能需要注意 Bigtable 中的物理数据布局。当查询速度很慢时,开发人员需要检查 Bigtable 跟踪以了解他们的查询执行低于预期的原因。 Megastore 不对块大小、压缩、表拆分、位置组或 Bigtable 提供的其他调整控制强制执行特定策略。相反,我们公开这些控件,为应用程序开发人员提供优化性能的能力(和负担)。

6. RELATED WORK

7. CONCLUSION

使用 Paxos 进行同步广域复制,为单个操作提供轻量级和快速的故障转移。
跨广泛分布的副本的同步复制的延迟损失被单个系统映像的便利性和运营商级可用性的运营优势所抵消。
在Bigtable的基础上提供了ACID事务、索引、队列。
将数据库划分为实体组子数据库可为大多数操作提供熟悉的事务功能,同时允许存储和吞吐量的可扩展性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值