1    背景

当前,使用业务和逻辑隔离的部署已经成为主流,但是对具体存储部署和接口的依赖,一直成为存储对业务逻辑完全透明的一个障碍。

为达到达到业务逻辑不必真正关心具体的存储逻辑,方便快速开发,便于日常维护,简化迁移等目的。对数据存储需要有一下的问题需要解决:

1.  抽象数据模型,统一数据访问接口,屏蔽业务层对数据层的逻辑依赖。同时提高业务的可维护性。

2.  解决当机房内的分布式数据相关问题,屏蔽业务层对数据部署,健康状态的依赖。同时提高对存储资源部署的灵活度。

3.  国际化跨IDC数据高可用性,互通,一致等问题。

4.  资源调度,资源隔离,屏蔽具体业务对资源的依赖。同时提高全局的资源利用率,降低整体成本。

5.  提供更好的数据抽象,便于快速开发,同时促进数据访问模式一致。

在当前的规划下,我们准备设计实现一套数据访问中间件,达成上面的目标;为了更好的实现数据访问中间件,达到既定目标,对业界知名的一些服务,产品进行调研,一方面补充我们在过去的思考中没有涵盖的需求,另外一方面对我们具体的实施,给出有意义的参考建议。

 

2    PNUTS

2.1 Introduction

Yahoo的PNUTS是一个分布式带国际化方案的数据存储平台。PNUTS显然就深谙CAP之道,考虑到大部分web应用对一致性并不要求非常严格,在设计上放弃了对强一致性的追求。代替的是追求更高的availability,容错,更快速的响应调用请求等。

可扩展性

目前大多数的流行的web应用,对存储的可扩展性的需求主要是两部分:逻辑上的可扩展性和物理上的可扩展性。逻辑上的可扩展性:需要支持一个可扩展的数据引擎作支持,另外为数据提供非固定的表结构(schema-free);物理上的可扩展性:支持扩容/扩展资源的时候,需要尽量少的操作并且对系统的性能冲击尽量低。PNUTS在这两方面都做了相应的支持。

响应时间与地域

Yahoo的SNS服务大部分是全球化的服务,有很明显的地域性和要求低延迟的需求。假设某个应用的数据,具有全球化的信息,例如好友关系,需要在不一样的IDC内,做到低延迟访问,PNUTS通过多IDC部署副本的方式来支持,保证在不用地域的用户能够快速访问到所需要的数据。

高可用性与容错

Yahoo的应用需要一个高可用性的存储服务做支持。提供高可用性,需要容错和一致性两方面做一个权衡。例如,在存储服务出错的时候,保证所有的应用都能够读取存储服务的数据,但是一些应用必须要在这时候进行写操作,这样就会产生数据不一致的风险。PNUTS对各方面容错进行了考虑,包括server出错,网络划分出错,IDC不可用等等。

弱一致性模型(最终一致)

传统的数据库需要支持序列化的事务,但是这里需要有个权衡,那就是:性能/可用性与一致性。按照我们的经验,大部分的Web应用,强一致性的需求都不是必要的。例如一个用户改变了他的头像,即便是他的好友不能马上看到这一修改,也不会造成什么严重的后果。PNUTS通过一种最终一致性(timeline consistency)支持,但也保留了强一致性的接口,可以通过该接口,强制读取最新的数据。

2.2 Architecture

图1 PNUTS架构

每个数据中心的PNUTS结构由四部分构成:

Storage Units (SU) 存储单元 物理的存储服务器,每个存储服务器上面含有多个tablets,tablets是PNUTS上的基本存储单元。一个tablets是一个yahoo内部格式的hash table的文件(hash table)或是一个MySQL innodb表(ordered table)。一个Tablet通常为几百M。一个SU上通常会存在几百个tablets。

Routers 每个tablets在哪个SU上是通过查询router获得。一个数据中心内router通常可由两台双机备份的单元提供。

Tablet Controller router的位置只是个内存快照,实际的位置由Tablet Controller单元决定。

Message Broker 与远程数据的同步是由YMB提供,它是一个pub/sub的异步消息订阅系统。

2.3 Reference Point

2.3.1 Data and Query Model

PNUTS主要是设计为在线存储服务,主要解决在线的大量单记录或者小范围记录集合的读和写访问。支持简单的关系型数据模型,并不支持大的二进制对象(如image、video),另外“blob”类型在PNUTS是支持的。

支持灵活的Schema,可以动态增加属性而不需要停查询或者更新,而且记录不需要对所有的属性赋值。目前只支持单表的查询,并且由应用指定两种索引方式(hash、ordered),支持单记录查询和范围查询。多表查询通过批量接口支持(接口需要给出多个表的key)。不支持join,group-by这类查询和事务,据说以后会支持。

2.3.2 Record-Level Mastering

为了达到实时响应的目的,在全球多idc的情况下,PNUTS不能使用写对所有副本进行更新的方案,这种方案只能应用在服务部署在同一个idc的情况下。然而,不是所有的读操作都需要马上读到当前的最新的版本,所以PNUTS选择了一种使得所有高延迟操作都走异步提交的方案来支持行级别的master。同步写多个副本(全球),可能需要秒级别的耗时。选用异步的方案,至少在同一个分区下的数据,能够做到低耗时,而记录级别的Master,能够满足更多需求,包括局部实时更新与读操作。

2.3.3 Consistency Model

PNUTS的一致性模型是基于传统的序列化和最终一致来设计的一个一致性协议:基于记录的timeline consistency,一个记录的所有副本的能够按照相同的顺序进行更新操作。例如图2的更新操作时序图:

图2 更新操作时序图

在图2中,对于一个主key,有insert,update和delete操作。任意副本的读操作会返回timeline上的一个版本记录,并且版本号会不断的按照这条timeline向前增加。在这个主key的所有副本中,其中有一个副本会被指定成master。master可以根据地域分区的变化进行相应的适应切换。副本会维护一个版本号,在所有的写操作中,这个版本号会不断的递增。所有副本只一个时刻只有一个序列号(可能不是最新的),所有写操作走master(master肯定是最新的),在同步过程中,其余副本将更新到master的最新状态,从而达到最终一致。

2.3.4 Data Query Interface

通过timeline consistency模型,PNUTS支持了各种层级的一致性保证,提供几类数据查询接口,分别是:Read-any,Read-critical,Read-lastest,Write,Test-and-set-write。

Read-any:返回一个合法版本的记录。这个接口获取的记录不一定是最新的,但一定是某个历史记录的合法版本。通过这个接口,能够得到低latency。大部分的web应用都不需要关注数据更新后是否需要实时能够看到,例如获取一个好友的状态,这个接口就很适合。

Read-critical(required_version):返回一个比required_version新或者相同的记录版本。一个应用场景是:当一个应用更新一条记录,然后他希望能马上读到这条记录的某个版本,能体现出他的更新操作。PNUTS的更新操作会返回更新后的版本号,通过这个版本号调用这个接口,就能够满足上述的应用场景。

Read-lastest:返回某条记录的最新的版本。很明显,如果master的分区和调用者的分区不在一个地方,那么这个接口会引起高latency。不过在几次访问后,record master会进行切换。

Write:更新某条记录。直接更新master,对单条记录,保证ACID和事务。

Test-and-set-write(required_version):执行更新操作,在某条记录的当前版本和required_version相同的时候。这个接口的应用场景:先读一条记录,然后做一个基于这个读操作的更新操作,如访问计数。这个接口,保证两个并发的更新事务能够被序列化。

2.3.5 Hosting

PNUTS是一个带域名的为多个应用提供服务的带中心管理的分布式数据存储。以服务的形式提供数据的管理支持,减少应用的开发时间,应用开发者不需要关系存储的scalable, reliable data management,从而达到快速开发的目的。通过服务的形式提供存储服务,还能够在运维上做很多事情,包括统一运维,监控,扩容等到,还有系统的升级,使得应用开发者所关注的事情更加少,从而能专注于业务开发。

PNUTS提供RESTful的API,支持应用的开发。

2.4 Summary

PNUTS的架构主要是基于行级的记录,并且所有数据有一个异步的分区副本,通过消息队列进行分区副本间的同步。PNUTS的一致性模型支持应用传统应用的一些feature,但是不支持一次提交时候全序化。另外,PNUTS是一个hosted服务,通过hosted服务进行数据传输的管理。

PNUTS的设计有下面的点值得我们数据访问中间件参考:

(1)行级别的Master

(2)timeline一致性模型

(3)IDC间的数据同步方案

(4)meta的数据存储方案

(5)用户接口,REST api

3   Dynamo

3.1 Introduction

Amazon的Dynamo是一个分布式的数据存储平台。Amazon一直致力于存储新技术的开发,Dynamo在这方面相当有代表性,Dynamo的推出,给整个存储界,带来了很大的震撼,NoSQL越来越多的吸引了人们的眼球。随后,Cassandra, BeansDB, Newclear等等,纷纷出现。

Dynamo的设计中,对可靠性,可扩展性要求极为苛刻;同时Dynamo不同于其他的存储平台,设计中高度进行去中心化,各个分布式结点低耦合,采取面向服务的方式对外提供kv服务;另外,Dynamo提供了方式,让应用进行可用性,一致性,消费比,性能方面的权衡。

3.1.1 Partitioning

为了适应容量和性能方面的无线扩展性,Dynamo毫无异议的使用了partition,不同的是,Dynamo使用了“consistent hashing”的方式进行Decentralized的分布。

3.1.2 CAP

Dynamo提供了最终数据一致性的保证;同时保证即使在发生了结点意外下线,网络隔离的情况下,都提供持续的可写性。在操作上,对更新采取乐观的更新,每次更新带上一个version;读时可自定义的冲突解决;quorum-like的更新同步复制策略。

3.1.3 Decentralized

在运行时,一个Dynamo中没有明显的层次结构,也没有核心结点,的每个实例都是对等的。

3.1.4 多IDC互通和一致

Dynamo的设计中,能够满足数据的跨IDC的互通和最终一致。即使在发生了网络隔离的条件下,仍然在各个区段都提供读写的高可用性,并且在网络恢复后,保证数据的最终一致性。

但是Dynamo的设计中要求的跨IDC的数据通路是高速的,这点和我们的现实情况有差距。并且在实现了跨机房的高可用性的同时,必须放弃W+R>N的“quorum-like”数据保证。

3.2 Architecture

在整个的Dynamo使用了高度Decentralized的设计,所有的结点,在运行是没有任何的区别。如此扁平的架构,导致Dynamo内部,看不出来太多的层次结构,相反,更多的设计集中于结点对一些时间处理的策略上。

3.2.1 功能

1.  纯粹的简单无模式kv,读,写,小数据(<1MByte)

2.  操作仅仅只是一次读写,不存在事务概念,同时弱化一致性,提高高可用性。

3.  严格的存储性能要求(高并发下仍然提供单次延迟的稳定)

4.  面向内部友好环境,不提供访问控制

5.  “面向服务”的接口方式,put/get over http

6.  一套dyanmo实例,仅仅提供为一种数据提供服务。不同的服务需要使用多个dynamo实例,每个实例设计拥有上百个结点。

3.2.2 Partitioning & Replication

首先,对于一个物理结点,会被虚拟化为多个的结点,然后虚拟结点形成对264-1的key范围的一个hash环。将一个物理结点虚拟成多个虚拟结点,能够让hash环上的结点分布更加均匀;同时发生调整时,造成的震荡也会相对均匀的分散于各个结点上;同时能够根据一个物理结点的处理能力,配置不同的数量的虚拟结点来区别对待不同的物理结点。

如上所示,是一个7个虚拟结点构成的一个换,在Dynamo中,

定义了常量N,用来表示一个数据会在多少个结点中进行存储。对于一个Key,K,其会存储于其顺时针方向后的N个结点上。

假设N = 3, 对于上图中的K,会被存储与,B, C, D三个结点上,并且B被称为K的“coordinator”。D会存储(A, D]范围的所有数据。(A, B] ∪ (B, C] ∪ (C, D]

对于N个结点,会自动对同一个物理结点的虚拟结点进行去重操作。

3.2.3 R & W

在Dynamo中,定义了其他的几个常量 R 和 W。分别代表参与读操作的最小结点数和写操作的最小结点数。

对于任意的一个K的读写,首先会被转发到对应的“coordinator”,由这个“coordinator”来主导这次的读写。

对于写操作,一定要求W(包括其自身)个结点写成功才算是一次成功的写,并返回成功。

对于读炒作,“coordinator”会将这个读转发至剩下的N-1个副本,并获取至少R(包含自身)个副本(带version),然后交由“divergent”来进行冲突的处理,并返回。

在Dyanmo的设计中,如果保证了R+W>N,就可以得到一个“quorum-like”的系统,在这样的环境下,读或者写对延时将是同步的并发R或者W个结点操作的耗时,对R/W/N之间关系的调整,让应用有了对系统进行权衡的方式。

3.2.4 Conflict Reconciliation

Dynamo要求及时发生了网络隔离之后,仍然提供不间断的读写服务。必然给数据的最终一致带来挑战,相较于一般的消极的写时避免冲突(conflict prevention),Dynamo提供的是乐观的在读时进行冲突解决(conflict reconciliation)。

为了完成这样的目的,在进行数据写入时,会给记录添加“object version”,在Dynamo中,允许在同一时间内,保持同一个key的多个version并存;一般在对一个key进行更新操作的时候,要求提供此次更新的“上一版本号”,用于在Dynamo中清除老的实例。版本号的定义为[node, counter],称为一个clock, 不同结点作为coordinator来主导的更新,都会被记录到一个向量之中,形成所谓的“vector clocks”。不同的结点作为coordinator来对一个Key进行修改时,仅仅修改向量的一个维度。

如果发生了网络隔离,必然会导致在一个Dynamo实例中同时包含了多个version,在读取时,如果发现了多个版本,由应用自定义的方案进行冲突的解决。比较好的方式是对多次写入的结果进行合并,例如电子商务中的多次下单,默认的会使用最新的版本。

下面是一个具体的例子:

假设一个Key,在正常情况下,会由结点Sx来负责其coordinator工作,在两次更新每次都会带上上次的version,会失效掉系统中旧的版本,整个Dynamo实例中,仅仅只有一个实例D2([Sx2])。

假设此时发生了调整,导致原有的这个Dynamo实例,同时有Sy和Sz同时负责不同分区内的K的coordinator工作,于是当两个分区各有更新的时候,在不同的隔离区上会形成不同的时钟向量,记Sy和Sz主导更新后,分别形成D3([Sx2], [Sy1])和D4([Sx2], [Sz1])当网络连通,用户端再此读取K是,会得到D2和D3,其向量为([Sx2], [Sy1] , [Sz1]), 如果再次由Sx进行对K的coordinator工作来写入,会得到新的数据D4([Sx3], [Sy1] , [Sz1]).

在上面的过程中,Sx, Sy, Sz在向量中,都有自己的位置,相互不会进行修改。

3.2.5 结点调整

当发生结点调整(人为或者意外)的时候,数据的分片会自动的在Dynamo实例中进行调整。保证原有的数据仍然保持N个副本。

还是已上面的Hash环,(A, B, C, D, E, F, G) N=3为例,

如果A正常下线,对于下面区间的Key,对应的N个结点(preference list)会发生相应的变化:

(E, F] : F G A -> F G B

(F, G] : G A B -> G B C

(G, A] : A B C -> B C D

也就是说,原来结点A上的数据(E, F], (F, G], (G, A]分别需要迁移至B, C, D上。

如果在A和B之间上线了新的结点X,对应下面区间的Key的“preference list”会发生变化:

(F, G] : G A B -> G A X

(G, A] : A B C -> A X B

(A, X] : B C D -> X B C

也就是说,原来的B, C, D上的(F, G], (G, A], (A, X]会迁移至结点X,让X上拥有(F, X]的数据。

另外对于意外的发生,如果A意外下线,这个时候D会完全接管对A的访问,同时,D会将原本发送给A的副本进行保存,当A得到恢复后,D会将这些副本转发给A,D也会删除这些数据,从而恢复原有的结构。

在设计中,Dynamo这样除了能够应对但结点的意外失效,更能够应对发生网络隔离,IDC范围失败等意外情况。因为Dynamo采用了虚拟结点的方式进行组织,能够保证在各个IDC内,都维护有全量副本,一个Key的preferred list也分布于多个IDC内。这势必要求运行时跨IDC的高速连接。

为了能够及时的诊断一个系统中结点的健康状况,Dynamo中还包括了一套结点发现,失效检测的机制,这里不进行详细的展开。

3.3Reference Point

3.3.1 设计点

1.  在数据分片,冗余和复制

在数据分片上,使用了一个变种的“一致性hash”;同一个key的数据,被冗余至连续的N个虚拟节点。

节点加入和推出时,发生数据迁移应对变化。

提供在线服务时,要求至少W个节点的写成功,R个节点的读成功,W+R>N

2.  最终一致,永远可写,读时冲突解决

Dynamo及时发生网络隔离的时候,仍然提供持续的写更新有效。这必然带来版本冲突,Dynamo在解决这方面问题的时候,使用的是“读时”冲突解决。为每个记录添加一个版本号(写时提供上一版本号,不匹配时,产生分支),发生冲突的时候,由应用在多版本下决定如何解决。默认使用最新版本。

3.  容量,性能持续横向扩展

4.  去中心化

3.3.2 类似产品

Cassandra

3.4 Summary

首先,dynamo的应用场景为全量的Get和Put,不涉及部分更新的问题,所以在很多地方处理相对简单。不用考虑一个更新涉及多个点的问题。但是这样的使用方式,并不能完全适用于社区类的业务数据。

最终一致性,在很多时候,对大社区业务,意义相对较少(PNuts提供的一致性保证可能更有意义)。但是社区应用,普遍的对一致性要求较少。

分片和复制,采用了去中心化,节点完全对称,给整个系统的结构带来了优化。但是其多份同步写,多份同步读的更新,带来了读写延迟以及写并发量的成本。

Dynamo的机制,如果发生网络隔离,实际上每个机房都有W+R>N是没有办法保证的,并且对于“高速网络连接的多IDC”这一假设,可能在我们的实际应用中,并不成立。如果多机房分别部署,数据的一致性又会成为问题。

由于每个业务使用自己的Dynamo实例,并且仅限于内部使用,不需要进行任何访问控制。

4   Amazon SimpleDB

4.1 Introduction

Amazon的SimpleDB是其AWS的重要组成部分,SimpleDB以Web Service的形式对外提供实时的结构化数据的实时服务。SimpleDB和S3,EC2一起提供完整的云计算环境。SimpleDB的在业界可谓非常有影响力,直接导致了开源项目CouchDB的展开。

4.1.1 易于使用

传统的关系型数据库,拥有较多的复杂的功能,并且大多不为web站点所使用;SimpleDB提供了精简的数据模型和简单的操作。

4.1.2 灵活,可扩展

在数据模型方面,无模式的数据模型,用户不必定义任何的数据scheme,用户只需要按需进行存储就可以了,系统会对用户数据进行索引,面对小量的数据不会有性能压力;用户在进行数据使用上是方便灵活的,数据的纵向扩展非常方便。在横向扩展方面,SimpleDB使用新建Domain的方式进行,SimpleDB内部,Domain之间是没有关系的,有具体的web应用来进行数据的组织。

4.1.3 高速可靠

对用户的数据的完全索引,保证个各种访问的高效性。同时,由Amazon保证了数据本身的高可用性和安全性。

但是考察Domain的建立方式,SimpleDB服务,还是面向单IDC服务的。

4.1.4 作为aws的一部分而设计

SimpleDB是作为aws的一部分进行设计的,专门用来存储一个应用的元数据,这些数据的消费端是业务逻辑。而Amazon的S3是专门用来存储“数据对象”的,这些数据作为一个整体进行消费,而不进行业务逻辑的解释;更多的,S3上的数据可以被直接用户浏览器所消费。

4.1.5 数据模型

SimpleDB使用的是“文档”结构的模型,提供了Account, Domain, Item, Attribute, Value,这样的5层结构,每层之间的关系是一对多的关系。

其中Account->Domain[]的一对多的关系主要提供用户对不同的数据进行管理,在一个Domain一般用于存储同一类的数据,类似于关系数据模型中的一个表。

一个Domain内部,有多个Item,每个Item类似于关系数据模型中的一行,每个Item有多个Attribute,类似于列,区别的是,每个Item的Attribute运行不一样;最后一个Attribute可以有多个Value。

4.2 Architecture

针对simpleDB的设计,能够获取的还是比较少的,现在仅仅对已有的资料做出一些评估,并给出自己的结论。对于更加具体的分析,可以参考后面的CouchDB方面的调研。

4.2.1 功能

1.  存储的http接口,SOAP & REST

2.  在同一个账户下,非关系型数据模型,数据模型为Domain,Item,Attribute,Values;在同一个Attribute下面,可以有多个Value.可以看作是一个文档模型。在其上,自动建立索引(SELECT时使用)。

3.  操作原语非常简单,对于数据操作,仅仅有

a)   PutAttribute/BatchPutAttribute

b)   DeleteAttribute

c)   GetAttributes

d)   Select(针对Attribute的,支持分页)

4.  容量

a)   单“Domain”数据量10GByte,“Attribute”数量109

b)   单“Item”键值对256

5.  提供最终一致性,读取时,支持“读最新”模式

4.2.2 具体实施

SimpleDB使用Erlang进行编写。

4.3 Reference Point

4.3.1 设计点

考虑SimpleDB的创建方式:

Choose a Region for your Domain(s) to optimize for latency, minimize costs, or address regulatory requirements. Amazon SimpleDB is currently available in the US East (Northern Virginia), US West (Northern California), EU (Ireland), and Asia Pacific (Singapore) Regions.

—http://aws.amazon.com/simpledb/

SimpleDB应该是不支持多IDC数据互通的。如果需要,应该是由上层的应用,进行数据跨IDC的冗余和数据组合,具体的应用中,靠谱吗?

SimpleDB,对每个“Domain”保存了多份的数据冗余,考虑到整体的数据量(10GByte), 在单机放内部,提供任何方式的一致性保证,应该都不会是一个难点。

SimpleDB,一个“Domain”下的数据,仅仅10GByte,所以正常的应用,仅仅在容量一侧层面,就会限制住很多体面的应用,因此,使用SimpleDB进行partition,除了常见,更是必须。Amazon给出的要求是,让应用层进行数据的拼装…一个账户可以控制100个“Domain”,这个限制应该比较随意,毕竟,一个账户下的和多个账户下的,在具体的数据组织上应该没有太多的区别。

SimpleDB最值得说的,除了报道上的商业上的成功,SimpleDB最值得称道的应该算他的数据模式了。文档模型,省掉了没有必要的SQL复杂操作。同时支持自动的索引建立,对于数据使用者,基本上没有任何的“做数据”的需求。针对这种任意多索引具体实施方式上面,没有什么资料进行透露(利用检索的思路?)。

4.3.2 类似产品

CouchDB

4.4Summary

SimpleDB的一些具体的问题:

1.  单“Domain”10GByte的容量

2.  对partition几乎没有任何的支持

3.  对多IDC几乎没有任何的支持

值得我们借鉴的,应该是SimpleDB的数据模式。

这样文档模型,自动的索引机制。

但是Select中,并没有对ORDER BY的支持,所以在不同的访问模式下,可能需要建立不同的Domain进行数据的存储(tieba的FRS类似的需求)。

SimpleDB的存储模式非常值得我们学习和思考,但是考虑到,在这样的文档化结构模型,和传统的关系型模型在操作上,还是有着非常大的不同,使用者能够进行过渡,还是一个值得置疑的地方。同时真的对每个字段进行检索,在具体的实现上是否现实,也是值得考虑的,而且由于字段仅仅限于字符串,而不同的数据类型在进行转化时,比较关系是会发生改变的(99<100, “99”>”100”)遮掩的检索是否真的满足需求也不一定。

5    Amazon S3

5.1 Introduction

Amazon S3,简单而言,是Amazon云体系下的kv存储服务。设计的初衷,是为了屏蔽掉分布式存储的细节,使得网络应用的开发来得跟进简单和快速。

Amason S3提供一个简单的Web服务接口,可用于在任意时间,任意地点的网页存储和检索任意数量的数据,提供高可用性,可靠安全的数据存储,快速廉价的基础设施。开发者可以通过Amason S3来存储自己业务相关的数据。

S3主要关注以下的feature:

1.读、写和删除对象的大小从1byte-5T。对象的数量不做限制。

2.每个对象会被存储在一个bucket中,通过一个唯一的key获取对象。

3.一个bucket能够只能被存储在n个分区的其中一个,分区数据不能在线互通。用户可以根据延迟、花费($)、地域管理成本等方面来综合考虑分区的选择。一个对象被存储在一个分区,那么它就只能存在那个分区中,除非显式的进行数据迁移(给$-.-)。

4.有权限机制,对象可以设置成公有,私有,或者给指定的用户访问。

5.提供标准的REST接口和SOAP接口。

6.默认协议是,HTTP协议。

5.2 Architecture

无相关实现资料,但是根据其一致性模型和实现的feature,估计和mola差不多。

5.3 Reference Point

5.3.1 Design Requirement

可扩展性:在扩展性方面,S3支持存储容量的无限扩展,以支持应用的存储。扩展的方法是优先为系统自动的增加节点,以达到可用性、速度、吞吐量、容量上的满足。

可靠性:据首页官方数据,数据的持久存储能做到99.999999999%不丢数据,并且有99.99%的可用性。不存在单点故障不可用的情况。所有错误都能够做冗余并且被修复。

范围速度:S3需要满足Web应用的高速范围的需求。服务器端的延时和外网的延迟无关。任何的性能瓶颈可以通过简单的增加机器节点来解决。

廉价:S3由廉价的硬件组件所构成。所有的硬件都有可能随时挂掉,但是保证不会影响整个系统的运行。

简单:S3要能做到可扩展性,可靠性,快速访问并且廉价的存储。而且需要给应用一个易用的接口与访问环境。

5.3.2 Data Type & Concepts

S3的数据类型非常简单,只有Buckets 和 Objects。

Buckets:一个Bucket是存储在S3上的Objects的容器。每个对象都存储在某一个Bucket当中,例如,如果一个对象的名字叫做:photos/puppy.jpg,存储在johnsmith Bucket当中,那么这个访问地址就是:

http://johnsmith.s3.amazonaws.com/photos/puppy.jpg 使用Bucket的目的有几个:在最高层次定义S3的命名空间,可以定义账号并且为存储和数据传输服务;能够做角色权限的控制;可以做更好的管理操作与配置

Objects:Objects是S3的基本存储实体。Objects包括Object data和Object metadata。Data部分封装在S3内,而Metadata则是一系列的名值对用于描述对象。

Keys:一个key是一个Object在一个Bucket里的唯一标识。每个Object在一个Bucket只有一个key。通过组合一个bucket,key和versionID,可以唯一标识每个对象,因此,s3的object可以简单的映射为“bucket+key+version”。

5.3.3 Data Consistency Model

S3使用的最终一致性模型。更新一个唯一的key是原子的。例如,如果对一个存在的Key进行PUT操作(更新),一个序列化的读操作有可能会读到老的数据也或者是更新后的数据,但是不会有写坏或者写部分数据的情况。

S3为了实现高可用,在多个服务器会部署副本。如果返回“success”,则数据会被安全的存储。然而,更新的改变不会马上的被同步到所有的副本中。

5.3.4 User Interface

S3提供REST接口和SOAP接口。具体可见附录[3]。

5.4 Summary

Amason S3主要是提供了一个kv的云存储服务。对于S3,数据转换中间件的参考点有:

(1)bucket+object+version概念;

(2)User Interface 参考;

(3)S3对需求满足的考虑。

6   Apache CouchDB

6.1 Introduction

开源的CouchDB,可以看作是SimpleDB的开源版本。基本上说CouchDB是一个使用RESTful接口文档模型的数据服务器;其数据模型采用文档结构,无模式。面向高可用性设计,CouchDB也采用分布式部署,自带了复制,数据冲突检测以及冲突解决机制。

6.1.1 文档数据模型

在CouchDB中,记录的基本元素为Document(类似于SimpleDB中,Domain内的一个Item),每个Item有多个Field(类似于SimpleDB中的Attribute),每个Field可以有一个或者多个Value。

一个CouchDB就是一个Document的集合,每个Document有一个唯一的ID进行标识。

利用这样面向文档的数据模型,使用CouchDB的应用,不用预先定义表模式,也不需要进行将来需求变更后带来的做数据的麻烦。

6.1.2 试图模型

CouchDB提供了试图功能(View)。和一般关系数据库的视图一样,都是基于底层具体的数据通过运算的方式来动态建立的。CouchDB提供过利用JavaScript进行试图的描述。除此以外,CouchDB的每一个视图,在实际的作用上,更是一个索引。

6.1.3 分布式设计

CouchDB在分布式方面,使用了基于对等的设计。多个CouchDB的结点部署在一起形成一个CouchDB集群的时候,每个结点都会保留一份全量的备份。每个结点都提供全量的数据服务(query, add, edit, delete)。结点之间的是双向的对其他结点上的更新进行增量同步。

如此的并发写环境下,会伴有写冲突(conflict),CouchDB拥有内建的冲突检测和管理机制。

6.1.4 和SimpleDB的区别

CouchDB和SimpleDB的在定位上非常相似,在这里做一番对别。

1.  相似点

a)   文档结构,无模式数据模型

b)   http访问接口

c)   Erlang所编写

d)   支持分布式部署

2.  区别

a)   具体接口上,SimpleDB除了RESTful接口,还有SOAP接口,并且使用Get方式,参数具体字段说明操作。CouchDB完全RESTful化,使用HTTP中的GET,POST,PUT, DELETE进行方法说明,URL进行数据库的选择。

b)   SimpleDB使用UTF-8编码的字符串作为原子数据类型,CouchDB使用JSON数据类型。

c)   SimpleDB默认对每个Attribute建立索引,CouchDB则利用视图手动建立索引

d)   由于字段类型的原因,支持的查询修饰操作也会不一样。

e)   SimpleDB使用XML形式返回查询结果,CouchDB使用JSON格式返回。

6.2 Architectrue

在大的部署方面,CouchDB使用扁平化的结构,也就是每个CouchDB结点都是对等的,没有具体的层次。各个结点之间通过“Replicator”来进行连接,同步彼此之间的更新动作。

下面是一个CouchDB内部的结构,也是整个CouchDB的功能的主要部分。

上面可以看出,整个CouchDB使用Erlang作为其编程语言,运行于Erlang虚拟机上。同时复用了大量的组件,例如http组件,JAVASCRIPT引擎,Lucene搜索引擎。

6.2.1 ACID特性,数据安全

CouchDB的读写完全支持ACID特性。

在磁盘数据的使用方面,CouchDB使用MVCC来保证ACID特性,同时CouchDB不会对update的旧数据进行覆盖,还是继续保留。这种状况下,任何的读写操作,都是在系统特定的一个snapshort下进行的,所以也不会有任何的锁状态。保证了高并发量。

具体的存储中,根据Document的DocID和SeqID进行b-tree的索引。每一此对一个Document的更新,都会对其进行一次SeqID的分配,b-tree索引也会被即使的进行更新至磁盘。

递增的SeqID可以用来增量的查看具体Document的变化。同时,对b-tree索引的修改,也总是发生在文件的末端,这样可以提高整个系统的写性能。

不对久数据进行覆盖,能够保证发生任何的以外的时候,CouchDB总是能够回到早些时候的一个一致状态。但是可能导致文件的过度膨胀。

通过一个“compaction”操作,能够回收这些不是最新状态下的Document的磁盘空间。这个时候,让我想到了comdb的类似操作。

6.2.2 View

对文档模型存储的数据,同时为了方便各种模式进行数据的访问,引入了视图(View)机制。

视图建立与实际的Document之上,需要多少种对数据的使用方式,就可以在的底层document的基础上,建立多少中视图。

在CouchDB中,是利用一种特殊的Document来存储对试图的定义的,因此和普通的Document一样,对View的修改,也会在集群中进行复制分发,从而让整个CouchDB集群都拥有一样的试图。

视图的定义,其实就是一个JavaScript方法,这个方法的参数就是一个Document对象,代表原始的底层数据,这个JavaScript函数根据这个输入,向View返回0个或多个自定义的Document对象。

如上面的结构图所示,CouchDB中,有一个“View Engine”进行视图的索引的维护和增量更新。索引更新利用了前面提到的SeqID,视图引擎为每个View维护了一个更新到的SeqID,在打开一个View时,和当前的SeqID进行比较,然后将没有更新进入索引的SeqID进行更新。整个扫描过程是一个顺序读盘过程,比较有效率。如果View在使用初期就定义,能够随着数据的增长来进行增量的变化,但是数据已经形成一定规模后,新建索引,会是一个消费很多资源的操作。

由于SeqID起到的MVCC作用,对于View的查询和更新完全并发,不会出现互斥的现象。和Ducoment的写方式一样,索引的更新也是集中于索引文件的尾部,一方面更新非常有效率,另外就算发生了以外,总是有一个最后有效的索引,配合SeqID进行恢复即可。

6.2.3 访问控制和数据验证

CouchDB拥有一套访问控制机制,拥有Administrator账户,并可创建读,写权限的账户。CouchDB会在进行数据访问的时候,利用这样一套用户体系进行访问控制;除了ACL,CouchDB还会同时进行数据格式的验证,对于ACL出错或者数据合法性校验出错的访问,会及时的报错,并回馈给访问者(client或者其他replica)。

6.2.4 分布式设计

CouchDB是一套“peer-based”的分布式数据库系统,外界可以并发的通过任何一个结点访问一整套完整的数据,而后CouchDB内部,通过点对点的更新增量复制让整个系统达到一致。

CouchDB内部的同步复制包括了数据,view上定义,用户体系等各个方面。并且允许长时间的网络隔离不同的结点各自更新后,最后同步到一个统一的状态。(一个典型的应用就是一个集中的服务器CouchDB集群作为主干,笔记本上都有一个CouchDB作为副本的场景,工作人员在自己的笔记本上进行工作,定期和服务器上内容进行同步)。

同时,复制也可以通过JavaScript进行过滤,从而进行部分数据的同步。

由于采用对等的分布式结构,同时发生隔离时的仍然支持读写可用,因此难以避免的会有冲突。CouchDB将数据冲突当作是一种常态来处理。具体的冲突处理策略可以是手动的或是自动进行。冲突处理的单位为Document,发生冲突后,每个CouchDB的结点,会采用统一的策略保留一个“winner”,从而保持数据的一致。同时由于CouchDB对数据处理的方式,这些“loser”不会被物理抹除,仍然可以获取得到。

6.3 Reference Point

6.3.1 设计点

CouchDB虽然和SimpleDB在很多层次上都比较类似,但是由于CouchDB公布了更多的细节,让我们在系统的设计上,能够有更多的学习。

1.  数据模型

和SimpleDB一样,使用了面向文档的数据模型,无模式。这样的模式,为数据的扩展提供了模型层面的保证。

2.  数据组织,并发模型

CouchDB对数据的组织中,不会对历史的数据进行抹除,充分保证了数据的安全性。在任何一个点上,都能保证灾难后都能够回到一个一致状态。同时由于使用了MVCC并且不删除旧版本,在数据本身上,不会有任何的读写冲突发生。

3.  分布式处理,跨IDC部署

CouchDB在分布式的结构上,使用去中心化的设置,自动恢复能力比较强。同时对于网络隔离有非常强的适应能力。

CouchDB,将冲突处理作为常态化的东西,保证了在网络隔离的条件下,仍然能够保证读写的可用性,同时一旦网络链路恢复,能够进行冲突解决,并且让整个分布式数据系统达到高度的一致性。这点在跨IDC的数据互通方面,显得非常有意义。

4.  逻辑处理脚本化

在CouchDB中,大量的使用了JavaScript进行简单的逻辑功能的描述。很多配置很难解决的问题,用脚本进行描述,能够很大程度提高灵活性。这在传统的关系数据库中,往往是使用类似存储过程进行处理,使用比较通用的JavaScript,门槛较低。

6.3.2类似产品

SimpleDB

MongoDB(内建了sharding机制)

RavenDB

6.4 Summary

考虑CouchDB在定位上和SimpleDB非常类似,但是其设计思想在一个分布式环境下,显得更为成熟,同时其暴露出来的内部实现也非常有意义。

数据访问效率和安全方面,数据的更新总是采取追加更新,实时写盘,更新的磁盘io是顺序的,能够保证很高的效率,同时正常服务状态下,是不会对旧数据进行抹除和覆盖的,保证了任何重启点,都能够回到上一个“数据一致”状态。同时在此基础上的MVCC,保证了一次访问的ACID特性。

但是还有有一些值得质疑的地方,实时的最佳写,如果没有每次更新有不多,如何保证读取时索引的使用效率。同时,如果作为服务的方式对外发布,让应用控制“Compaction”有点不太实际,至少在使用方式上不符合习惯。当然这些底层的数据实现,对数据访问中间件的借鉴意义不大。

对脚本的使用方面。作为一个比较大的系统,总是有很多的和数据相关的过滤,注入等加工需求,往往配置很难满足这样的一些需求;在传统的关系数据库系统中的存储过程和触发器这些,又显得较重;CouchDB使用JavaScript这样的脚本,显得更为灵活,而且JavaScript的门槛较低,从事站点开发的人员更为熟悉。

在分布式的部署方面,完全采用去中心化的方式,防灾能力较强。同时利用冲突解决机制,保证任何时刻的读写可用,对于跨IDC的数据互通非常有意义,但是冲突解决往往也是说起来容易,做起来会比较复杂。CouchDB自带了在冲突版本中进行选择的功能,同时也能够让上层应用决定。但是冲突解决还是有以下的问题:

1.  应用解决策略本身;如果由CouchDB自行解决,肯定很难保证总是让人满意;如果应用自己设定在开发习惯上,往往并不合适,而且增加了应用方面的难度和负担;同时在复杂的环境下,程序很难涵盖到所有的情况。

2.  冲突解决的粒度;CouchDB使用的是按照Document进行冲突解决,但是一个不一致更重要的是逻辑上的,单个Document上没有冲突,但是并不意味着更大层面上没有发生不一致。假设我们将一个文章分段进行了存储,如果更新分散进行,及时每段没有冲突,但是发生组合的时候并不一定就顺畅了。而且对于一个Document的冲突,往往需要其他的数据支持才能够进行实现冲突的解决。

这也让人想到代码版本控制方面的问题,如果让机器自信进行不同分支的merge,总是会出问题的。

虽然很难在数据层面完全利用冲突处理来满足高可用的需求,但是对于一些特殊的应用,我们完全可以利用这一思想来提供高可用性的特殊服务(例如用户名到用户id,空间url到空间id的全局排他分配等)。

7    ZooKeeper

对zookeeper的调研停留在使用层面上。

7.1 Introduction

和google的chubby类似,zookeeper是一套分布式部署的分布式服务,提供一些简单的操作,在其上,适合用于分布式系统的同步,配置维护,分组和名字服务,以及元数据存储。

当前,百度对Zookeeper已经积累了比较丰富的使用经验。

7.2 Architecture

1.  访问接口上,zookeeper提供了一组对外的api,使用namespace/znode的数据模型,类似于一般文件系统的。

2.  容量上,zookeeper实例中的所有结点,镜像存储了全量数据,数据存放于内存中。

3.  部署上,zookeeper分布式部署,一个zookeeper实例需要多个节点,由zookeeper自己的机制保证数据的复制和同步。

4.  对zookeeper上的数据的操作,拥有“全局序列”,保证上层对分布式事务的支持。

7.3 Reference Point

7.3.1 设计点

1.  整个系统能够要求消息的“Atomic Broadcast”,从而保证每个节点,在状态上的同步。

zxid对FIFO的保证。

2.  整个系统分为两个运行阶段“Leader Activation”和“Active Messaging”

a)   “Leader Activation”,确定正系统中,节点的关系,一个leader,多个followers;选主,“Paxos”算法的变种。

b)   “Active Messaging”,对API消息的处理,对于查询,直接在follower上进行,对于更新,都会汇集到leader上,在复制至各个follower.

7.4 Summary

当前,zookeeper已经在百度有多处应用。

1.  Galiproxy资源定位代理,并已集成至connect-pool,ubserver/ubclient

2.  Autoexchange单点自动切换系统

3.  Roshan Zookeeper Server管理前端

4.  DDBS利用其进行资源定位,元数据存储

5.  同时,百度特有的transfer也会和我们自己的资源定位库结合在一起

在数据访问中间件中,可以利用zookeeper进行资源发现,元数据存储等工作。

8   注册方面网络互通调研

当前能够获得到资料的,仅仅只有国内的几个站点,基本的做法都是强烈依赖与关系数据库的一致性保证,对我们的借鉴意义很少。

对于其他的国际化产品,这方面缺乏调研资料。

9    综述

前面,对当前业界比较典型和广为讨论的存储方面的产品做了简单的调研,业界的一些前端成果和概念,和我们的实际差别比较大,有一部分值得我们借鉴和学习,有一部分需要我们进行剖析和取舍。下面根据我们前期对各个产品线的调研情况,对我们这次调研的结论进行一个综述。

9.1 数据模型,访问接口

这次调研的数据存储产品方面,数据模型可谓非常的丰富,有关系的,类似文件的,还有面向文档的。

在众多的访问模型中,面向文档的数据模型无论是在适应性,灵活性,将来面临的调整成本,在理想情况下,对于业务而言,都是非常理想的,同时也为“NOT Only SQL”打足了底气。但是,面临这样的数据模型,但是在使用习惯,接口成熟度方面,都还有待于被大家所接受;同时数据横向扩展方面,文档数据库还缺乏这方面的经验。此外,调研了当前比较各个产品线,基本上KV和拉链就可以概括所有的需求,但是最好能够做到更新KV是级联更新拉链。在扩展方面,往往需要字段方面的扩展。而sql接口,完全可以涵盖这些模型的操作。

因此为了方便产品的使用,现实的使用mysql的二进制接口作为数据访问中间层的接口。同时在后期加强字段可扩展方面的工作,提炼出KV,拉链两种模型。

9.2 分布式结构,数据互通,一致性机制

在我们前面的调研中,所有的存储都支持了分布式的存储。其中PNuts使用了层次结构,而Dynamo, CouchDB等,使用了对等的设计。层次结构分工明确,对等设计让整体结构扁平,易于部署,单单个节点内工作变得很复杂。在结构设计上,我们并不追求任何类型的部署,但是简单,稳定,可伸缩式我们进行整个结构设计的准则。

在一致性模型方面,PNuts采用的是按照存储单元的“时序一致性”,主要采用消极的冲突避免机制,对于网络隔离发生时,可能存在一定的写不可用;而Dynamo,CouchDB这样的一些存储,为了保证任何时刻的读写可用,使用了乐观的写冲突解决机制;前面已经说过,从现实的角度出发,对于冲突解决,并没有想象那么理想,不管是技术还是习惯方面都会有一定的难度,同时PNuts的一套模型下,发生隔离时,还是保证了绝大部分的可用性,可以满足应用需求,在存储中间件的设计方面,我们会大体上使用PNuts的模型,这样的模型,更符合大社区对数据一致性的要求,但是将时序一致性方面扩展到逻辑上的一个“Partition”。

在多IDC互通方面与地域支持,只有PNuts考虑了这方面的需求,通过行级别的master和timeline一致性支持,由消息队列同步idc间的数据,并且提供丰富的一致性接口支持。

9.3 相关组件

在相关的组件方面,也提供了一些原型。PNuts的设计之中,各个组件之间,使用YMB作为消息链路。在我们的设计中,也会使用逻辑上的消息队列进行各个组件之间的数据链接。考虑到百度自己的资源和使用经验,我们会在BMQ,cm/transfer,FIFO进行选型,考虑到其实这三者最后对会统一到一个组件上,我们基本决定使用cm/transfer作为一个逻辑上的消息队列,后期替换为同一的一个组件。

此外,我们会使用现有的ZooKeeper作为我们单IDC内的元数据存储,资源发现的基础,结合式我们的Galiproxy,transfer等机制。