大规模分布式存储系统:原理解析与架构实战——读书摘抄
大规模分布式存储系统:原理解析与架构实战
杨传辉
分布式系统的数据量远远超出了单个计算机的存储和处理能力。
一个2亿用户的电信运营商,如果平均每个用户每天拨打接听总共10个电话,每个电话400字节,5年的话费记录总量即为0.2G×10×0.4K×365×5=1.46PB。除了分布式系统,人们还很难有其他高效的手段来存储和处理这些PB级甚至更多的数据。
单机和分布式有两个明显的不同:
首先,分布式环境下会出现一部分计算机工作正常,另一部分计算机工作不正常的情况,程序需要在这种情况下尽可能地正常工作,这个挑战非常大。
其次,单机环境下的函数调用常常可以在微秒级内返回,所以除了少数访问外部设备(例如磁盘、网卡等)的函数采用异步方式调用外,大部分函数采用同步调用的方式,编译器和操作系统在调用前后自动保存与恢复程序的上下文;在分布式环境下,计算机之间的函数调用(远程调用,即RPC)的返回时间通常是毫秒或亚毫秒(0.1~1.0毫秒)级,差不多是单机环境的100倍,使用同步方式远远不能发挥现代CPU处理器的性能,所以分布式环境下的RPC通常采用异步调用方式,程序需要自己保存和恢复调用前后的上下文,并需要处理更多的异常。
著名公司
为了处理这些海量内容,每个互联网公司在后端都有一套成熟的分布式系统用于数据的存储、计算以及价值提取。
Google的核心技术正是后端这些处理海量数据的分布式系统。和Google类似,国外的亚马逊、微软以及国内互联网三巨头阿里巴巴、百度和腾讯的核心技术也是其后端的海量数据处理系统。
互联网公司的分布式存储系统由数量众多的、低成本和高性价比的普通PC服务器通过网络连接而成。
本书作者2007年年底加入百度公司,从事大规模分布式存储的研究和实践工作,曾经开发过类似GFS、MapReduce和Bigtable的分布式系统,后来转战阿里巴巴继续开发分布式数据库OceanBase。
分布式存储是基础,云存储和大数据是构建在分布式存储之上的应用。
移动终端的计算能力和存储空间有限,而且有在多个设备之间共享资源的强烈的需求,这就使得网盘、相册等云存储应用很快流行起来。云存储的核心还是后端的大规模分布式存储系统。
大数据则更近一步,不仅需要存储海量数据,还需要通过合适的计算框架或者工具对这些数据进行分析,抽取其中有价值的部分。如果没有分布式存储,便谈不上对大数据进行分析。
分布式存储系统
分布式存储系统可以扩展到几百台甚至几千台的集群规模,而且,随着集群规模的增长,系统整体性能表现为线性增长。
分布式存储系统的挑战主要在于数据、状态信息的持久化,要求在自动迁移、自动容错、并发读写的过程中保证数据的一致性。
分布式存储涉及的技术主要有:
数据分布:如何将数据分布到多台服务器才能够保证数据分布均匀
一致性:如何将数据的多个副本复制到多台服务器,即使在异常情况下,也能够保证不同副本之间的数据一致性
容错:检测服务器故障并自动将出现故障的服务器上的数据和服务迁移到集群中的其他服务器
负载均衡:新增服务器和集群运行中实现负载均衡
分布式事务,多版本并发控制
数据分类
非结构化数据:包括所有格式的办公文档、文本、图片、图像、音频和视频信息等。
结构化数据:一般存储在关系数据库中,可以用二维关系表结构来表示。结构化数据的模式(Schema,包括属性、数据类型以及数据之间的联系)和内容是分开的,数据的模式需要预先定义。
不同的分布式存储系统适合处理不同类型的数据
分布式存储系统分为四类:
1、分布式文件系统
互联网应用需要存储大量的图片、照片、视频等非结构化数据对象,这类数据以对象的形式组织,对象之间没有关联,这样的数据一般称为Blob(Binary Large Object,二进制大对象)数据。分布式文件系统用于存储Blob对象。
在系统实现层面,分布式文件系统内部按照数据块(chunk)来组织数据
2、分布式键值系统
分布式键值系统用于存储关系简单的半结构化数据,它只提供基于主键的CRUD(Create/Read/Update/Delete)功能,即根据主键创建、读取、更新或者删除一条键值记录。
从数据结构的角度看,分布式键值系统与传统的哈希表比较类似,不同的是,分布式键值系统支持将数据分布到集群中的多个存储节点。
一般用作缓存,比如Memcache。
一致性哈希是分布式键值系统中常用的数据分布技术。
3、分布式表格系统
分布式表格系统用于存储关系较为复杂的半结构化数据,与分布式键值系统相比,分布式表格系统不仅仅支持简单的CRUD操作,而且支持扫描某个主键范围。分布式表格系统以表格为单位组织数据,每个表格包括很多行,通过主键标识一行,支持根据主键的CRUD功能以及范围查找功能。
分布式表格系统借鉴了很多关系数据库的技术,例如支持某种程度上的事务。
与分布式数据库相比,分布式表格系统主要支持针对单张表格的操作,不支持一些特别复杂的操作,比如多表关联,多表联接,嵌套子查询;另外,在分布式表格系统中,同一个表格的多个数据行也不要求包含相同类型的列,适合半结构化数据。
4、分布式数据库
分布式数据库一般是从单机关系数据库扩展而来,用于存储结构化数据。分布式数据库采用二维表格组织数据,提供SQL关系查询语言,支持多表关联,嵌套子查询等复杂操作,并提供数据库事务以及并发控制。
典型的系统包括MySQL数据库分片(MySQL Sharding)集群,阿里巴巴OceanBase系统也是一个支持自动扩展的分布式关系数据库。
存储引擎
存储引擎就是哈希表、B树等数据结构在机械磁盘、SSD等持久化介质上的实现。单机存储系统是单机存储引擎的一种封装,对外提供文件、键值、表格或者关系模型。
哈希存储引擎是哈希表的持久化实现,……
数据库将一个或多个操作组成一组,称作事务,事务必须满足原子性、一致性、隔离性以及持久性。
为了保证持久性,对于数据库的每一个变化都要在磁盘上记录日志,当数据库系统突然发生故障,重启后能够恢复到之前一致的状态。
摩尔定律:每18个月计算机等IT产品的性能会翻一番;或者说相同性能的计算机等IT产品,每18个月价钱会降一半。
早期的CPU为单核芯片,现代服务器基本为多核或多个CPU
经典的多CPU架构为对称多处理结构,即在一个计算机上汇集了一组处理器,它们之间对称工作,无主次或从属关系,共享相同的物理内存及总线。
SMP系统的每个CPU有两个核心(core),CPU与内存之间通过总线通信。每个核心有各自的L1d Cache(L1数据缓存)及L1i Cache(L1指令缓存),同一个CPU的多个核心共享L2以及L3缓存。
某些CPU还可以通过超线程技术(Hyper-Threading Technology)使得一个核心具有同时执行两个线程的能力。
现在的主流服务器架构一般为NUMA(Non-Uniform Memory Access,非一致存储访问)架构。它具有多个NUMA节点,每个NUMA节点是一个SMP结构,一般由多个CPU(如4个)组成,并且具有独立的本地内存、IO槽口等。NUMA节点可以直接快速访问本地内存,也可以通过NUMA互联互通模块访问其他NUMA节点的内存。
以Intel x48主板为例,它是典型的南、北桥架构。北桥芯片通过前端总线(Front Side Bus,FSB)与CPU相连,内存模块以及PCI-E设备(如高端的SSD设备Fusion-IO)挂接在北桥上
网卡(包括千兆以及万兆网卡),硬盘以及中低端固态盘(如Intel 320系列SSD)挂接在南桥上。
传统的数据中心网络拓扑
接入层交换机、汇聚层以及核心层的交换机。
由于同一个接入层的服务器往往部署在一个机架内,因此,设计系统的时候需要考虑服务器是否在一个机架内,减少跨机架拷贝大量数据。例如,Hadoop HDFS默认存储三个副本,其中两个副本放在同一个机架,就是这个原因。
Google在2008年的时候将网络改造为扁平化拓扑结构,即三级CLOS网络,同一个集群内最多支持20480台服务器,且任何两台都有1Gb带宽。CLOS网络需要额外投入更多的交换机,带来的好处也是明显的,设计系统时不需要考虑底层网络拓扑,从而很方便地将整个集群做成一个计算资源池。
同一个数据中心内部的传输延时是比较小的,网络一次来回的时间在1毫秒之内。数据中心之间的传输延迟是很大的,取决于光在光纤中的传输时间。例如,北京与杭州之间的直线距离大约为1300公里,光在信息传输中走折线,假设折线距离为直线距离的1.5倍,那么光传输一次网络来回延时的理论值为1300×1.5×2/300000=13毫秒,实际测试值大约为40毫秒。
存储系统的性能瓶颈一般在于IO。顺序读取1MB数据的时间为:磁盘寻道时间+数据读取时间,即10ms+1MB/100MB/s×1000=20ms。存储系统的性能瓶颈主要在于磁盘随机读写。
固态磁盘(SSD),各大互联网公司都有大量基于SSD的应用。SSD的特点是随机读取延迟小,能够提供很高的IOPS(每秒读写,Input/Output Per Second)性能;主要问题在于容量和价格。
磁盘适合大块顺序访问的存储系统,SSD适合随机访问较多或者对延时比较敏感的关键系统。
从分布式系统的角度看,整个集群中所有服务器上的存储介质(内存、机械硬盘,SSD)构成一个整体,其他服务器上的存储介质与本机存储介质一样都是可访问的,区别仅仅在于需要额外的网络传输及网络协议栈等访问开销
集群中有30个机架,每个机架接入40台服务器,同一个机架的服务器接入到同一个接入交换机,不同机架的服务器接入到不同的接入交换机。
哈希存储引擎不支持顺序扫描
B树(B-Tree)存储引擎支持顺序扫描,对应的存储系统是关系数据库。
LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎一样,支持增、删、改、随机读取以及顺序扫描。它通过批量转储技术规避磁盘随机写入问题,广泛应用于互联网的后台存储系统,例如Google Bigtable、Google LevelDB以及Facebook开源的Cassandra系统
Bitcask是一个基于哈希表结构的键值存储系统,它仅支持追加操作(Append-only),即所有的写操作只追加而不修改老的数据。在Bitcask系统中,每个文件有一定的大小限制,当文件增加到相应的大小时,就会产生一个新的文件,老的文件只读不写。在任意时刻,只有一个文件是可写的,用于数据追加,称为活跃数据文件。
Bitcask数据文件中的数据是一条一条的写入操作,每一条记录的数据项分别为主键(key)、value内容(value)、主键长度(key_sz)、value长度(value_sz)、时间戳(timestamp)以及crc校验值。(数据删除操作也不会删除旧的条目,而是将value设定为一个特殊的值用作标识)。内存中采用基于哈希表的索引数据结构,哈希表的作用是通过主键快速地定位到value的位置。哈希表结构中的每一项包含了三个用于定位数据的信息,分别是文件编号(file id),value在文件中的位置(value_pos),value长度(value_sz),通过读取file_id对应文件的value_pos开始的value_sz个字节,这就得到了最终的value值。写入时首先将Key-Value记录追加到活跃数据文件的末尾,接着更新内存哈希表,因此,每个写操作总共需要进行一次顺序的磁盘写入和一次内存操作。
系统基于一个假设,value的长度远大于主键的长度。假如value的平均长度为1KB,每条记录在内存中的索引信息为32字节,那么,磁盘内存比为32:1。这样,32GB内存索引的数据量为32GB×32=1TB。
Bitcask系统中的记录删除或者更新后,原来的记录成为垃圾数据。如果这些数据一直保存下去,文件会无限膨胀下去,为了解决这个问题,Bitcask需要定期执行合并(Compaction)操作以实现垃圾回收。所谓合并操作,即将所有老数据文件中的数据扫描一遍并生成新的数据文件,这里的合并其实就是对同一个key的多个操作以只保留最新一个的原则进行删除,每次合并后,新生成的数据文件就不再有冗余数据了。
Bitcask系统中的哈希索引存储在内存中,如果不做额外的工作,服务器断电重启重建哈希表需要扫描一遍数据文件,如果数据文件很大,这是一个非常耗时的过程。Bitcask通过索引文件(hint file)来提高重建哈希表的速度。索引文件就是将内存中的哈希索引表转储到磁盘生成的结果文件。
相比哈希存储引擎,B树存储引擎不仅支持随机读取,还支持范围扫描
叶子节点保存每行的完整数据
B+树一次检索最多需要h-1次磁盘IO,复杂度为O(h)=O(logdN)(N为元素个数,d为每个节点的出度,h为B+树高度)
修改操作首先需要记录提交日志,接着修改内存中的B+树。如果内存中的被修改过的页面超过一定的比率,后台线程会将这些页面刷到磁盘中持久化
缓冲区管理器负责将可用的内存划分成缓冲区,缓冲区是与页面同等大小的区域,磁盘块的内容可以传送到缓冲区中。缓冲区管理器的关键在于替换策略,即选择将哪些页面淘汰出缓冲池。
LSM树(Log Structured Merge Tree)的思想非常朴素,就是将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操作批量写入磁盘,读取时需要合并磁盘中的历史数据和内存中最近的修改操作。LSM树的优势在于有效地规避了磁盘随机写入问题,但读取时可能需要访问较多的磁盘文件
LevelDB存储引擎主要包括:内存中的MemTable和不可变MemTable(Immutable MemTable,也称为Frozen MemTable,即冻结MemTable)以及磁盘上的几种主要文件:当前(Current)文件、清单(Manifest)文件、操作日志(Commit Log,也称为提交日志)文件以及SSTable文件。当应用写入一条记录时,LevelDB会首先将修改操作写入到操作日志文件,成功后再将修改操作应用到MemTable,这样就完成了写入操作。
当MemTable占用的内存达到一个上限值后,需要将内存的数据转储到外存文件中。LevelDB会将原先的MemTable冻结成为不可变MemTable,并生成一个新的MemTable。新到来的数据被记入新的操作日志文件和新生成的MemTable中。顾名思义,不可变MemTable的内容是不可更改的,只能读取不能写入或者删除。LevelDB后台线程会将不可变MemTable的数据排序后转储到磁盘,形成一个新的SSTable文件,这个操作称为Compaction。SSTable文件是内存中的数据不断进行Compaction操作后形成的,且SSTable的所有文件是一种层级结构,第0层为Level 0,第1层为Level 1,以此类推。
SSTable中的文件是按照记录的主键排序的,每个文件有最小的主键和最大的主键。LevelDB的清单文件记录了这些元数据,包括属于哪个层级、文件名称、最小主键和最大主键。当前文件记录了当前使用的清单文件名。在LevelDB的运行过程中,随着Compaction的进行,SSTable文件会发生变化,新的文件会产生,老的文件被废弃,此时往往会生成新的清单文件来记载这种变化,而当前文件则用来指出哪个清单文件才是当前有效的。
直观上,LevelDB每次查询都需要从老到新读取每个层级的SSTable文件以及内存中的MemTable。LevelDB做了一个优化,由于LevelDB对外只支持随机读取单条记录,查询时LevelDB首先会去查看内存中的MemTable,如果MemTable包含记录的主键及其对应的值,则返回记录即可;如果MemTable没有读到该主键,则接下来到同样处于内存中的不可变Memtable中去读取;类似地,如果还是没有读到,只能依次从新到老读取磁盘中的SSTable文件。
数据模型
文件、关系以及随着NoSQL技术流行起来的键值模型、关系弱化的表格模型
文件系统以目录树的形式组织文件,以类UNIX操作系统为例,根目录为/,包含/usr、/bin、/home等子目录,每个子目录又包含其他子目录或者文件
POSIX(Portable Operating System Interface)是应用程序访问文件系统的API标准,它定义了文件系统存储接口及操作集。
POSIX标准适合单机文件系统,在分布式文件系统中,出于性能考虑,一般不会完全遵守这个标准。NFS(Network File System)文件系统允许客户端缓存文件数据,多个客户端并发修改同一个文件时可能出现不一致的情况。举个例子,NFS客户端A和B需要同时修改NFS服务器的某个文件,每个客户端都在本地缓存了文件的副本,A修改后先提交,B后提交,那么,即使A和B修改的是文件的不同位置,也会出现B的修改覆盖A的情况。
对象模型与文件模型比较类似,用于存储图片、视频、文档等二进制数据块,典型的系统包括Amazon Simple Storage(S3),Taobao File System(TFS)。这些系统弱化了目录树的概念,Amazon S3只支持一级目录,不支持子目录,Taobao TFS甚至不支持目录结构。与文件模型不同的是,对象模型要求对象一次性写入到系统,只能删除整个对象,不允许修改其中某个部分。
SQL查询还有一个强大的特性是允许在WHERE、FROM和HAVING子句中使用子查询,子查询又是一个完整的select-from-where语句。
大量的NoSQL系统采用了键值模型(也称为Key-Value模型),Key-Value模型过于简单,支持的应用场景有限,NoSQL系统中使用比较广泛的模型是表格模型。
表格模型弱化了关系模型中的多表关联,支持基于单表的简单操作,典型的系统是Google Bigtable以及其开源Java实现HBase。表格模型除了支持简单的基于主键的操作,还支持范围扫描,另外,也支持基于列的操作。与关系模型不同的是,表格模型一般不支持多表关联操作,Bigtable这样的系统也不支持二级索引,事务操作支持也比较弱,各个系统支持的功能差异较大,没有统一的标准。另外,表格模型往往还支持无模式(schema-less)特性,也就是说,不需要预先定义每行包括哪些列以及每个列的类型,多行之间允许包含不同列。
关系数据库在海量数据场景面临如下挑战:1、事务关系模型要求多个SQL操作满足ACID特性,所有的SQL操作要么全部成功,要么全部失败。在分布式系统中,如果多个操作属于不同的服务器,保证它们的原子性需要用到两阶段提交协议,而这个协议的性能很低,且不能容忍服务器故障,很难应用在海量数据场景。2、联表传统的数据库设计时需要满足范式要求,例如,第三范式要求在一个关系中不能出现在其他关系中已包含的非主键信息。假设存在一个部门信息表,其中每个部门有部门编号、部门名称、部门简介等信息,那么在员工信息表中列出部门编号后就不能加入部门名称、部门简介等部门有关的信息,否则就会有大量的数据冗余。而在海量数据的场景,为了避免数据库多表关联操作,往往会使用数据冗余等违反数据库范式的手段。实践表明,这些手段带来的收益远高于成本。
关系数据库采用B树存储引擎,更新操作性能不如LSM树这样的存储引擎。另外,如果只有基于主键的增、删、查、改操作,关系数据库的性能也不如专门定制的Key-Value存储系统。
数据库事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)以及持久性(Durability)
多个事务并发执行时,如果它们的执行结果和按照某种顺序一个接着一个串行执行的效果等同,这种隔离级别称为可串行化。可串行化是比较理想的情况,商业数据库为了性能考虑,往往会定义多种隔离级别。事务的并发控制一般通过锁机制来实现,锁可以有不同的粒度,可以锁住行,也可以锁住数据块甚至锁住整个表格
为了提高读事务性能,可以采用写时复制(Copy-On-Write,COW)或者多版本并发控制(Multi-Version Concurrency Control,MVCC)技术来避免写事务阻塞读事务。
原子性,事务的原子性也体现在事务对数据的读取上,例如,一个事务对同一数据项的多次读取的结果一定是相同的。
一致性,事务需要保持数据库数据的正确性、完整性和一致性,一般情况下银行账务余额不能是负数,信用卡消费不能超过该卡的信用额度等。
隔离性,数据库需要保证每一个事务在它的修改全部完成之前,对其他的事务是不可见的,换句话说,不能让其他事务看到该事务的中间状态。从银行账户A转一笔款项a到账户B,不能让其他事务(例如账户查询)看到A账户已经扣除款项a但B账户却还没有增加款项a的状态。
永久性,事务完成后,它对于数据库的影响是永久性的,即使系统出现各种异常也是如此。
出于性能考虑,许多数据库允许使用者选择牺牲隔离属性来换取并发度,从而获得性能的提升。SQL定义了4种隔离级别。
隔离级别的降低可能导致读到脏数据或者事务执行异常
锁也分为两种类型:读锁以及写锁,允许对同一个元素加多个读锁,但只允许加一个写锁,且写事务将阻塞读事务。这里的元素可以是一行,也可以是一个数据块甚至一个表格
T1和T2两个事务操作不同行,初始时A=B=25,T1将A加100,T2将B乘以2,由于T1和T2操作不同行,两个事务没有锁冲突,可以并行执行而不会破坏系统的一致性。
T1扫描从A到C的所有行,将它们的结果相加后更新A,初始时A=C=25,假设在T1执行过程中T2插入一行B,那么,事务T1和T2无法做到可串行化。为了保证数据库一致性,T1执行范围扫描时需要锁住从A到C这个范围的所有更新,T2插入B时,由于整个范围被锁住,T2获取锁失败而等待T1先执行完成。
多个事务并发执行可能引入死锁。表2-6中T1读取A,然后将A的值加100后更新B,T2读取B,然后将B的值乘以2更新A,初始时A=B=25。T1持有A的读锁,需要获取B的写锁,而T2持有B的读锁,需要A的写锁。T1和T2这两个事务循环依赖,任何一个事务都无法顺利完成。
解决死锁的思路主要有两种:第一种思路是为每个事务设置一个超时时间,超时后自动回滚,表2-6中如果T1或T2二者之中的某个事务回滚,则另外一个事务可以成功执行。第二种思路是死锁检测。死锁出现的原因在于事务之间互相依赖,T1依赖T2,T2又依赖T1,依赖关系构成一个环路。检测到死锁后可以通过回滚其中某些事务来消除循环依赖。
互联网业务中读事务占的比例往往远远超过写事务,很多应用的读写比例达到6:1,甚至10:1。写时复制(Copy-On-Write,COW)读操作不用加锁,极大地提高了读取性能。
1)拷贝:将从叶子到根节点路径上的所有节点拷贝出来。 2)修改:对拷贝的节点执行修改。 3)提交:原子地切换根节点的指针,使之指向新的根节点。
如果读操作发生在第3步提交之前,那么,将读取老节点的数据,否则将读取新节点,读操作不需要加锁保护
写时复制技术涉及引用计数,对每个节点维护一个引用计数,表示被多少节点引用,如果引用计数变为0,说明没有节点引用,可以被垃圾回收。
写时复制技术原理简单,问题是每次写操作都需要拷贝从叶子到根节点路径上的所有节点,写操作成本高
除了写时复制技术,多版本并发控制,即MVCC(Multi-Version Concurrency Control),也能够实现读事务不加锁。MVCC对每行数据维护多个版本,无论事务的执行时间有多长,MVCC总是能够提供与事务开始时刻相一致的数据。
以MySQL InnoDB存储引擎为例,InnoDB对每一行维护了两个隐含的列,其中一列存储行被修改的“时间”,另外一列存储行被删除的“时间”,注意,InnoDB存储的并不是绝对时间,而是与时间对应的数据库系统的版本号,每当一个事务开始时,InnoDB都会给这个事务分配一个递增的版本号,所以版本号也可以被认为是事务号。对于每一行查询语句,InnoDB都会把这个查询语句的版本号同这个查询语句遇到的行的版本号进行对比,然后结合不同的事务隔离级别,来决定是否返回改行。
如果行的修改或者删除版本号大于事务号,说明行是被该事务后面启动的事务修改或者删除的。在可重复读取隔离级别下,后开始的事务对数据的影响不应该被先开始的事务看见,所以应该忽略后开始的事务的更新或者删除操作。
MVCC读取数据的时候不用加锁,每个查询都通过版本检查,只获得自己需要的数据版本,从而大大提高了系统的并发度。当然,为了实现多版本,必须对每行存储额外的多个版本的数据。另外,MVCC存储引擎还必须定期删除不再需要的版本,及时回收空间。
故障恢复
数据库运行过程中可能会发生故障,这个时候某些事务可能执行到一半但没有提交,当系统重启时,需要能够恢复到一致的状态,即要么提交整个事务,要么回滚。数据库系统以及其他的分布式存储系统一般采用操作日志(有时也称为提交日志,即Commit Log)技术来实现故障恢复。操作日志分为回滚日志(UNDO Log)、重做日志(REDO Log)以及UNDO/REDO日志。
为了保证数据库的一致性,数据库操作需要持久化到磁盘,如果每次操作都随机更新磁盘的某个数据块,系统性能将会很差。因此,通过操作日志顺序记录每个数据库操作并在内存中执行这些操作,内存中的数据定期刷新到磁盘,实现将随机写请求转化为顺序写请求。
操作日志记录了事务的操作。例如,事务T对表格中的X执行加10操作,初始时X=5,更新后X=15,那么,UNDO日志记为<T,X,5>,REDO日志记为<T,X,15>,UNDO/REDO日志记为<T,X,5,15>。关系数据库系统一般采用UNDO/REDO日志。
存储系统如果采用REDO日志,其写操作流程如下: 1)将REDO日志以追加写的方式写入磁盘的日志文件。 2)将REDO日志的修改操作应用到内存中。 3)返回操作成功或者失败。
存储系统要求先将REDO日志刷入磁盘才可以更新内存中的数据,如果每个事务都要求将日志立即刷入磁盘,系统的吞吐量将会很差。因此,存储系统往往有一个是否立即刷入磁盘的选项,对于一致性要求很高的应用,可以设置为立即刷入;相应地,对于一致性要求不太高的应用,可以设置为不要求立即刷入,首先将REDO日志缓存到操作系统或者存储系统的内存缓冲区中,定期刷入磁盘。这种做法有一个问题,如果存储系统意外故障,可能丢失最后一部分更新操作。
成组提交(Group Commit)技术是一种有效的优化手段。REDO日志首先写入到存储系统的日志缓冲区中: a)日志缓冲区中的数据量超过一定大小,比如512KB; b)距离上次刷入磁盘超过一定时间,比如10ms。当满足以上两个条件中的某一个时,将日志缓冲区中的多个事务操作一次性刷入磁盘,接着一次性将多个事务的修改操作应用到内存中并逐个返回客户端操作结果。与定期刷入磁盘不同的是,成组提交技术保证REDO日志成功刷入磁盘后才返回写操作成功。这种做法可能会牺牲写事务的延时,但大大提高了系统的吞吐量。
如果所有的数据都保存在内存中,那么可能出现两个问题: ●故障恢复时需要回放所有的REDO日志,效率较低。如果REDO日志较多,比如超过100GB,那么,故障恢复时间是无法接受的。 ●内存不足
因此,需要将内存中的数据定期转储(Dump)到磁盘,这种技术称为checkpoint(检查点)技术。系统定期将内存中的操作以某种易于加载的形式(checkpoint文件)转储到磁盘中,并记录checkpoint时刻的日志回放点,以后故障恢复只需要回放checkpoint时刻的日志回放点之后的REDO日志。由于将内存数据转储到磁盘需要很长的时间,而这段时间还可能有新的更新操作,checkpoint必须找到一个一致的状态。checkpoint流程如下: 1)日志文件中记录"START CKPT"。 2)将内存中的数据以某种易于加载的组织方式转储到磁盘中,形成checkpoint文件。checkpoint文件中往往记录"START CKPT"的日志回放点,用于故障恢复。 3)日志文件中记录"END CKPT"。
传统的行式数据库将一个个完整的数据行存储在数据页中。如果处理查询时需要用到大部分的数据列,这种方式在磁盘IO上是比较高效的。
列式数据库是将同一个数据列的各个值存放在一起。插入某个数据行时,该行的各个数据列的值也会存放到不同的地方。
列式数据库大大地提高了OLAP大数据量查询的效率。当然,列式数据库不是万能的,每次读取某个数据行时,需要分别从不同的地方读取各个数据列的值,然后合并在一起形成数据行
很多列式数据库还支持列组(column group,Bigtable系统中称为locality group),即将多个经常一起访问的数据列的各个值存放在一起。如果读取的数据列属于相同的列组,列式数据库可以从相同的地方一次性读取多个数据列的值,避免了多个数据列的合并。列组是一种行列混合存储模式,这种模式能够同时满足OLTP和OLAP的查询需求。
由于同一个数据列的数据重复度很高,因此,列式数据库压缩时有很大的优势。例如,Google Bigtable列式数据库对网页库压缩可以达到15倍以上的压缩率
另外,可以针对列式存储做专门的索引优化。比如,性别列只有两个值,“男”和“女”,可以对这一列建立位图索引:如图2-12所示,“男”对应的位图为100101,表示第1、4、6行值为“男”;“女”对应的位图为011010,表示第2、3、5行值为“女”。如果需要查找男性或者女性的个数,只需要统计相应的位图中1出现的次数即可。另外,建立位图索引后0和1的重复度高,可以采用专门的编码方式对其进行压缩。
分布式系统面临的第一个问题就是数据分布,即将数据均匀地分布到多个存储节点。另外,为了保证可靠性和可用性,需要将数据复制多个副本,这就带来了多个副本之间的数据一致性问题。大规模分布式存储系统的重要目标就是节省成本,因而只能采用性价比较高的PC服务器。这些服务器性能很好,但是故障率很高,要求系统能够在软件层面实现自动容错。当存储节点出现故障时,系统能够自动检测出来,并将原有的数据和服务迁移到集群中其他正常工作的节点。
分布式系统中有两个重要的协议,包括Paxos选举协议以及两阶段提交协议。Paxos协议用于多个节点之间达成一致,往往用于实现总控节点选举。两阶段提交协议用于保证跨多个节点操作的原子性,这些操作要么全部成功,要么全部失败。
服务器宕机
重启后也需要恢复内存信息。
设计容错系统的一个基本原则是:网络永远是不可靠的,任何一个消息只有收到对方的回复后才可以认为发送成功,系统设计时总是假设网络将会出现异常并采取相应的处理措施。
磁盘故障分为两种情况:磁盘损坏和磁盘数据错误。
多台服务器,即使其中一台服务器磁盘出现故障,也能从其他服务器上恢复数据
对于磁盘数据错误,往往可以采用校验和(checksum)机制来解决
在分布式系统中,如果某个节点向另外一个节点发起RPC(Remote Procedure Call)调用,这个RPC执行的结果有三种状态:“成功”、“失败”、“超时”(未知状态),也称为分布式存储系统的三态。
当出现超时状态时,只能通过不断读取之前操作的状态来验证RPC操作是否成功。当然,设计分布式存储系统时可以将操作设计为“幂等”的,也就是说,操作执行一次与执行多次的结果相同,例如,覆盖写就是一种常见的幂等操作。
由于异常的存在,分布式存储系统设计时往往会将数据冗余存储多份,每一份称为一个副本。
副本是分布式存储系统容错技术的唯一手段。由于多个副本的存在,如何保证副本之间的一致性是整个分布式系统的理论核心。
一般来说,存储系统可以支持强一致性,也可以为了性能考虑只支持最终一致性。从客户端的角度看,一般要求存储系统能够支持读写一致性,会话一致性,单调读,单调写等特性。
常见的性能指标有:系统的吞吐能力以及系统的响应时间
这两个指标往往是矛盾的,追求高吞吐的系统,往往很难做到低延迟;追求低延迟的系统,吞吐量也会受到限制
如果系统部署在同一个数据中心,只要系统设计合理,在保证强一致性的前提下,不会对性能和可用性造成太大的影响。Alibaba的OceanBase系统以及Google的分布式存储系统都倾向强一致性。
生成一张有30张缩略图(假设图片原始大小为256KB)的页面需要多少时间?
●方案1:顺序操作,每次先从磁盘中读取图片,再执行生成缩略图操作,执行时间为:30×10ms(磁盘随机读取时间)+30×256K/30MB/s(假设缩略图生成速度为30MB/s)=560ms
●方案2:并行操作,一次性发送30个请求,每个请求读取一张图片并生成缩略图,执行时间为:10ms+256K/300MB/s=18ms
分布式系统区别于传统单机系统在于能够将数据分布到多个节点,并在多个节点之间实现负载均衡。数据分布的方式主要有两种,一种是哈希分布,如一致性哈希,代表系统为Amazon的Dynamo系统;另外一种方法是顺序分布,即每张表格上的数据按照主键整体有序,代表系统为Google的Bigtable系统。Bigtable将一张大表根据主键切分为有序的范围,每个有序范围是一个子表。
哈希取模的方法很常见,其方法是根据数据的某一种特征计算哈希值,并将哈希值与集群中的服务器建立映射关系,从而将不同哈希值的数据分布到不同的服务器上。所谓数据特征可以是key-value系统中的主键(key),也可以是其他与业务逻辑相关的值。例如,将集群中的服务器按0到N-1编号(N为服务器的数量),根据数据的主键(hash(key)%N)或者数据所属的用户id(hash(user_id)%N)计算哈希值,来决定将数据映射到哪一台服务器。
如果哈希函数的散列特性很好,哈希方式可以将数据比较均匀地分布到集群中去,然而,找出一个散列特性很好的哈希函数是很难的。这是因为,如果按照主键散列,那么同一个用户id下的数据可能被分散到多台服务器,这会使得一次操作同一个用户id下的多条记录变得困难;如果按照用户id散列,容易出现“数据倾斜”(data skew)问题,即某些大用户的数据量很大,无论集群的规模有多大,这些用户始终由一台服务器处理。
传统的哈希分布算法还有一个问题:当服务器上线或者下线时,N值发生变化,数据映射完全被打乱,几乎所有的数据都需要重新分布,这将带来大量的数据迁移。
一种思路是不再简单地将哈希值和服务器个数做除法取模映射,而是将哈希值与服务器的对应关系作为元数据,交给专门的元数据服务器来管理。访问数据时,首先计算哈希值,再查询元数据服务器,获得该哈希值对应的服务器。这样,集群扩容时,可以将部分哈希值分配给新加入的机器并迁移对应的数据。另一种思路就是采用一致性哈希。
哈希散列破坏了数据的有序性,只支持随机读取操作,不能够支持顺序扫描
顺序分布在分布式表格系统中比较常见,一般的做法是将大表顺序划分为连续的范围,每个范围称为一个子表,总控服务器负责将这些子表按照一定的策略分配到存储节点上。
用户表(User表)的主键范围为1~7000,在分布式存储系统中划分为多个子表,分别对应数据范围1~1000,1001~2000,……6001~7000
读User表时,需要通过Meta表查找相应的User子表所在的存储节点
分布式存储系统的每个集群中一般有一个总控节点,其他节点为工作节点,由总控节点根据全局负载信息进行整体调度。工作节点刚上线时,总控节点需要将数据迁移到该节点,另外,系统运行过程中也需要不断地执行迁移任务,将数据从负载较高的工作节点迁移到负载较低的工作节点。
分布式存储系统通过复制协议将数据同步到多个存储节点,并确保多个副本之间的数据一致性。
同一份数据的多个副本中往往有一个副本为主副本(Primary),其他副本为备副本(Backup),由主副本将数据复制到备份副本。
复制协议分为两种,强同步复制以及异步复制,二者的区别在于用户的写请求是否需要同步到备副本才可以返回成功。
一致性和可用性是矛盾的,强同步复制协议可以保证主备副本之间的一致性,但是当备副本出现故障时,也可能阻塞存储系统的正常写服务,系统的整体可用性受到影响;异步复制协议的可用性相对较好,但是一致性得不到保障,主副本出现故障时还有数据丢失的可能。
主副本将写请求复制到其他备副本,常见的做法是同步操作日志
假设所有副本的个数为N,且N>2,即备副本个数大于1。那么,实现强同步协议时,主副本可以将操作日志并发地发给所有备副本并等待回复,只要至少1个备副本返回成功就可以回复客户端操作成功。强同步的好处在于如果主副本出现故障,至少有1个备副本拥有完整的数据,分布式存储系统可以自动地将服务切换到最新的备副本而不用担心数据丢失的情况。
强同步复制和异步复制都是将主副本的数据以某种形式发送到其他副本,这种复制协议称为基于主副本的复制协议
在任何时刻只能有一个副本为主副本
NWR复制协议,其中,N为副本数量,W为写操作的副本数,R为读操作的副本数。NWR协议中多个副本不再区分主和备
只要W+R>N,可以保证读到的副本中至少有一个包含了最新的更新。然而,这种协议的问题在于不同副本的操作顺序可能不一致,从多个副本读取时可能出现冲突。这种方式在实际系统中比较少见,不建议使用。
存储系统设计时需要在一致性和可用性之间权衡,在某些场景下,不允许丢失数据,在另外一些场景下,极小的概率丢失部分数据时允许的,可用性更加重要。例如,Oracle数据库的DataGuard复制组件包含三种模式:
●最大保护模式(Maximum Protection):即强同步复制模式,
●最大性能模式(Maximum Performance):即异步复制模式
单台服务器故障的概率是不高的,然而,只要集群的规模足够大,每天都可能有机器故障发生
在分布式系统中,故障检测往往通过租约(Lease)协议实现
容错处理的第一步是故障检测,心跳是一种很自然的想法。假设总控机A需要确认工作机B是否发生故障,那么总控机A每隔一段时间,比如1秒,向工作机B发送一个心跳包。如果一切正常,机器B将响应机器A的心跳包;否则,机器A重试一定次数后认为机器B发生了故障。然而,机器A收不到机器B的心跳并不能确保机器B发生故障并停止了服务,在系统运行过程中,可能发生各种错误,比如机器A与机器B之间网络发生问题,机器B过于繁忙导致无法响应机器A的心跳包
租约机制就是带有超时时间的一种授权。假设机器A需要检测机器B是否发生故障,机器A可以给机器B发放租约,机器B持有的租约在有效期内才允许提供服务,否则主动停止服务。机器B的租约快要到期的时候向机器A重新申请租约。正常情况下,机器B通过不断申请租约来延长有效期,当机器B出现故障或者与机器A之间的网络发生故障时,机器B的租约将过期,从而机器A能够确保机器B不再提供服务,机器B的服务可以被安全地迁移到其他服务器。
单层结构的分布式存储系统维护了多个副本,例如副本个数为3,主备副本之间通过操作日志同步。某单层结构的分布式存储系统有3个数据分片A、B、C,每个数据分片存储了三个副本。其中,A1,B1,C1为主副本,分别存储在节点1,节点2以及节点3。假设节点1发生故障,将被总控节点检测到,总控节点选择一个最新的副本,比如A2或者A3替换A1成为新的主副本并提供写服务。
两层结构的分布式存储系统会将所有的数据持久化写入底层的分布式文件系统,每个数据分片同一时刻只有一个提供服务的节点。如图3-5所示,某双层结构的分布式存储系统有3个数据分片,A、B和C。它们分别被节点1,节点2和节点3所服务。当节点1发生故障时,总控节点将选择一个工作节点,比如节点2,加载A的服务。由于A的所有数据都存储在共享的分布式文件系统中,节点2只需要从底层分布式文件系统读取A的数据并加载到内存中。
总控节点自身也可能出现故障,为了实现总控节点的高可用性(High Availability),总控节点的状态也将实时同步到备机,当故障发生时,可以通过外部服务选举某个备机作为新的总控节点,而这个外部服务也必须是高可用的。为了进行选主或者维护系统中重要的全局信息,可以维护一套通过Paxos协议实现的分布式锁服务,比如Google Chubby或者它的开源实现Apache Zookeeper。
分布式存储系统中往往有一个总控节点用于维护数据分布信息,执行工作机管理,数据定位,故障检测和恢复,负载均衡等全局调度工作。
开源的Hadoop也能够扩展到3000台以上的集群。
同一个组内的节点服务相同的数据,这样的系统称为同构系统。同构系统的问题在于增加副本需要迁移的数据量太大,假设每个存储节点服务的数据量为1TB,内部传输带宽限制为20MB/s,那么增加副本拷贝数据需要的时间为1TB/20MB/s=50000s,大约十几个小时。
系统中有五个分片(A,B,C,D,E),每个分片包含三个副本,如分片A的三个副本分别为A1,A2以及A3。假设节点1发生永久性故障,那么可以从剩余的节点中任意选择健康的节点来增加A,B以及E的副本。由于整个集群都参与到节点1的故障恢复过程,故障恢复时间很短,而且集群规模越大,优势就会越明显。
异构系统
分布式系统涉及的协议很多,例如租约,复制协议,一致性协议
两阶段提交协议用于保证跨多个节点操作的原子性,也就是说,跨多个节点的操作要么在所有节点上全部执行成功,要么全部失败。两阶段提交协议(Two-phase Commit,2PC)经常用来实现分布式事务。
Paxos协议用于确保多个节点对某个投票(例如哪个节点为主节点)达成一致。
在两阶段协议中,系统一般包含两类节点:一类为协调者( coordinator),通常一个系统中只有一个;另一类为事务参与者( participants, cohorts 或 workers),一般包含多个。
1、阶段1:请求阶段(Prepare Phase)。在请求阶段,协调者通知事务参与者准备提交或者取消事务,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地执行成功)或者取消(事务参与者本地执行失败)。
2、阶段2:提交阶段(Commit Phase)。在提交阶段,协调者将基于第一个阶段的投票结果进行决策:提交或者取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行相应的操作。
A组织B、C和D三个人去爬长城:如果所有人都同意去爬长城,那么活动将举行;如果有一人不同意去爬长城,那么活动将取消。
假如D一直不能回复邮件,那么A、B和C将不得不处于一直等待的状态。并且B和C所持有的资源一直不能释放,
A可以通过引入事务的超时机制防止资源一直不能释放的情况。
更为严重的是,假如A发完邮件后生病住院了,即使B、C和D都发邮件告诉A同意下周三去爬长城,如果A没有备份,事务将被阻塞。
两阶段提交协议可能面临两种故障:
●事务参与者发生故障。给每个事务设置一个超时时间,如果某个事务参与者一直不响应,到达超时时间后整个事务失败。
●协调者发生故障。协调者需要将事务相关信息记录到操作日志并同步到备用协调者,假如协调者发生故障,备用协调者可以接替它完成后续的工作。如果没有备用协调者,协调者又发生了永久性故障,事务参与者将无法完成事务而一直等待下去。
大多数分布式存储系统都采用敬而远之的做法,放弃对分布式事务的支持。
Paxos协议用于解决多个节点之间的一致性问题。多个节点之间通过操作日志同步数据,如果只有一个节点为主节点,那么,很容易确保多个节点之间操作日志的一致性。考虑到主节点可能出现故障,系统需要选举出新的主节点。Paxos协议正是用来实现这个需求。
为了实现高可用性,主节点往往将数据以操作日志的形式同步到备节点。如果主节点发生故障,备节点会提议自己成为主节点。这里存在的问题是网络分区的时候,可能会存在多个备节点提议(Proposer,提议者)自己成为主节点。Paxos协议保证,即使同时存在多个proposer,也能够保证所有节点最终达成一致,即选举出唯一的主节点。
大多数情况下,系统只有一个proposer,他的提议也总是会很快地被大多数节点接受。Paxos协议执行步骤如下: 1)批准(accept):Proposer发送accept消息要求所有其他节点(acceptor,接受者)接受某个提议值,acceptor可以接受或者拒绝。 2)确认(acknowledge):如果超过一半的acceptor接受,意味着提议值已经生效,proposer发送acknowledge消息通知所有的acceptor提议生效。
当出现网络或者其他异常时,系统中可能存在多个proposer,他们各自发起不同的提议。
如果proposer第一次发起的accept请求没有被acceptor中的多数派批准(例如与其他proposer的提议冲突),那么,需要完整地执行一轮Paxos协议。过程如下:
Paxos协议需要考虑两个问题:正确性,即只有一个提议值会生效;可终止性,即最后总会有一个提议值生效。
Paxos协议有两种用法:一种用法是用它来实现全局的锁服务或者命名和配置服务,例如 Apache Zookeeper
另外一种用法是用它来将用户数据复制到多个数据中心
2PC协议最大的缺陷在于无法处理协调者宕机问题。如果协调者宕机,那么,2PC协议中的每个参与者可能都不知道事务应该提交还是回滚,整个协议被阻塞,执行过程中申请的资源都无法释放。因此,常见的做法是将2PC和Paxos协议结合起来,通过2PC保证多个数据分片上的操作的原子性,通过Paxos协议实现同一个数据分片的多个副本之间的一致性。另外,通过Paxos协议解决2PC协议中协调者宕机问题。当2PC协议中的协调者出现故障时,通过Paxos协议选举出新的协调者继续提供服务。
跨机房部署方案有三个:集群整体切换、单个集群跨机房、Paxos选主副本。
在前两种方案中,总控节点需要和工作节点之间保持租约(lease),当工作节点出现故障时,自动将它上面服务的主副本切换到其他工作节点。如果采用Paxos协议选主副本,那么,每个数据分片的多个副本构成一个Paxos复制组。B1、B2、B3、B4构成一个复制组,某一时刻B1为复制组的主副本,当B1出现故障时,其他副本将尝试切换为主副本,Paxos协议保证只有一个副本会成功。这样,总控节点与工作节点之间不再需要保持租约,总控节点出现故障也不会对工作节点产生影响。
Google文件系统(GFS)是构建在廉价服务器之上的大型分布式系统。
GFS是Google分布式存储的基石,其他存储系统,如Google Bigtable、Google Megastore、Google Percolator均直接或者间接地构建在GFS之上。另外,Google大规模批处理系统MapReduce也需要利用GFS作为海量数据的输入输出。
GFS系统的节点可分为三种角色:GFS Master(主控服务器)、GFS ChunkServer(CS,数据块服务器)以及GFS客户端。
客户端是GFS提供给应用程序的访问接口,它是一组专用接口,不遵循POSIX规范,以库文件的形式提供。客户端访问GFS时,首先访问主控服务器节点,获取与之进行交互的CS信息,然后直接访问这些CS,完成数据存取工作。
CDN通过将网络内容发布到靠近用户的边缘节点,使不同地域的用户在访问相同网页时可以就近获取。
所谓的边缘节点是CDN服务提供商经过精心挑选的距离用户非常近的服务器节点,仅“一跳”(Single Hop)之遥。用户在访问时就无需再经过多个路由器,大大减少访问时间。
DNS在对域名解析时不再向用户返回源服务器的IP,而是返回了由智能CDN负载均衡系统选定的某个边缘节点的IP。用户利用这个IP访问边缘节点,然后该节点通过其内部DNS解析得到源服务器IP并发出请求来获取用户所需的页面,如果请求成功,边缘节点会将页面缓存下来,下次用户访问时可以直接读取,而不需要每次都访问源服务器。
淘宝CDN系统用于支持用户购物,尤其是“双11”光棍节时的海量图片请求。图片存储在后台的TFS集群中,CDN系统将这些图片缓存到离用户最近的边缘节点。
相比分布式存储系统,分布式缓存系统的实现要容易很多。这是因为缓存系统不需要考虑数据持久化,如果缓存服务器出现故障,只需要简单地将它从集群中剔除即可。
由于Blob存储系统读访问量大,更新和删除很少,特别适合通过CDN技术分发到离用户最近的节点。
新上线的CDN缓存节点配备的磁盘均为SSD。
分布式键值模型可以看成是分布式表格模型的一种特例。然而,由于它只支持针对单个key-value的增、删、查、改操作,因此,适用哈希分布算法。
Amazon Dynamo以很简单的键值方式存储数据,不支持复杂的查询。
Dynamo写入数据时,首先,根据一致性哈希算法计算出每个数据副本所在的存储节点,其中一个副本作为本次写操作的协调者。接着,协调者并发地往所有其他副本发送写请求,每个副本将接收到的数据写入本地,协调者也将数据写入本地。当某个副本写入成功后,回复协调者。如果发给某个副本的写请求失败,协调者会将它加入重试列表不断重试。等到W-1个副本回复写入成功后(即加上协调者共W个副本写入成功),协调者可以回复客户端写入成功。协调者回复客户端成功后,还会继续等待或者重试,直到所有的副本都写入成功。
Dynamo读取数据时,首先,根据一致性哈希算法计算出每个副本所在的存储节点,其中一个副本作为本次读操作的协调者。接着,协调者根据负载策略选择R个副本,并发地向它们发送读请求。每个副本读取本地数据,协调者也读取本地数据。当某个副本读取成功后,回复协调者读取结果。等到R-1个副本回复读取成功后(即加上协调者共R个副本读取成功),协调者可以回复客户端。这里分为两种情况:如果R个副本返回的数据完全一致,将某个副本的读取结果回复客户端;否则,需要根据冲突处理规则合并多个副本的读取结果。Dynamo系统默认的策略是根据修改时间戳选择最新的数据,当然用户也可以自定义冲突处理方法。读取过程中如果发现某些副本上的数据版本太旧,Dynamo内部会异步发起一次读取修复操作,使用冲突解决后的结果修正错误的副本。
Dynamo设计支持可插拔的存储引擎,比如Berkerly DB(BDB),MySQL InnoDB等。
Dynamo采用无中心节点的P2P设计,增加了系统可扩展性,但同时带来了一致性问题,影响上层应用。另外,一致性问题也使得异常情况下的测试变得更加困难,由于Dynamo只保证最基本的最终一致性,多客户端并发操作的时候很难预测操作结果,也很难预测不一致的时间窗口,影响测试用例设计。
主流的分布式系统一般都带有中心节点,这样能够简化设计,而且中心节点只维护少量元数据,一般不会成为性能瓶颈。
无中心节点的设计短期之内难以成为主流
Tair是淘宝开发的一个分布式键/值存储引擎。Tair分为持久化和非持久化两种使用方式:非持久化的Tair可以看成是一个分布式缓存,持久化的Tair将数据存放于磁盘中。
Tair作为一个分布式系统,是由一个中心控制节点和若干个服务节点组成。其中,中心控制节点称为Config Server,服务节点称为Data Server。Config Server负责管理所有的Data Server,维护其状态信息;Data Server对外提供各种数据服务,并以心跳的形式将自身状况汇报给Config Server。Config Server是控制点,而且是单点,目前采用一主一备的形式来保证可靠性,所有的Data Server地位都是等价的。
根据数据的主键计算哈希值后,分布到Q个桶中,桶是负载均衡和数据迁移的基本单位。Config Server按照一定的策略把每个桶指派到不同的Data Server上。
当某台Data Server故障不可用时,Config Server能够检测到。每个哈希桶在Tair中存储多个副本,如果是备副本,那么Config Server会重新为其指定一台Data Server,如果是持久化存储,还将复制数据到新的Data Server上。如果是主副本,那么ConfigServer首先将某个正常的备副本提升为主副本,对外提供服务。接着,再选择另外一台Data Server增加一个备副本,确保数据的备份数。
机器加入或者负载不均衡可能导致桶迁移,迁移的过程中需要保证对外服务。当迁移发生时,假设Data Server A要把桶3、4、5迁移到Data Server B。迁移完成前,客户端的路由表没有变化,客户端对3、4、5的访问请求都会路由到A。现在假设3还没开始迁移,4正在迁移中,5已经迁移完成。那么如果对3访问,A直接服务;如果对5访问,A会把请求转发给B,并且将B的返回结果返回给用户;如果对4访问,由A处理,同时如果是对4的修改操作,会记录修改日志,等到桶4迁移完成时,还要把修改日志发送到B,在B上应用这些修改操作,直到A和B之间数据完全一致迁移才真正完成。
Tair默认包含两个存储引擎:Mdb和Fdb,此外,还支持Berkerly DB、Tokyo Cabinet、InnoDB、Leveldb等各种存储引擎。
Amazon Dynamo采用P2P架构,而在Tair中引入了中心节点Config Server。这种方式很容易处理数据的一致性,不再需要向量时钟、数据回传、Merkle树、冲突处理等复杂的P2P技术。另外,中心节点的负载很低。作者认为,分布式键值系统的整体架构应该参考Tair,而不是Dynamo。
Tair最主要的用途在于分布式缓存,持久化存储起步比较晚,在实现细节上也有一些不尽如人意的地方。例如,Tair持久化存储通过复制技术来提高可靠性,然而,这种复制是异步的。因此,当有Data Server发生故障时,客户有可能在一定时间内读不到最新的数据,甚至发生最新修改的数据丢失的情况。
分布式表格系统对外提供表格模型,每个表格由很多行组成,通过主键唯一标识,每一行包含很多列。整个表格在系统中全局有序,顺序分布。
Hadoop
Bigtable是Google开发的基于GFS和Chubby的分布式表格系统。Google的很多数据,包括Web索引、卫星图像数据等在内的海量结构化和半结构化数据,都存储在Bigtable中。
Bigtable系统由很多表格组成,每个表格包含很多行,每行通过一个主键(Row Key)唯一标识,每行又包含很多列(Column)。某一行的某一列构成一个单元(Cell),每个单元包含多个版本的数据。整体上看,Bigtable是一个分布式多维映射表。另外,Bigtable将多个列组织成列族(column family),这样,列名由两个部分组成:(column family,qualifier)。列族是Bigtable中访问控制的基本单元,也就是说,访问权限的设置是在列族这一级别上进行的。Bigtable中的列族在创建表格的时候需要预先定义好,个数也不允许过多;然而,每个列族包含哪些qualifier是不需要预先定义的,qualifier可以任意多个,适合表示半结构化数据。Bigtable中的行主键可以是任意的字符串,最大不超过64KB。Bigtable表中的数据按照行主键进行排序,排序使用的是字典序。行主键com.cnn.www是域名www.cnn.com变换后的结果,这样做的好处是使得所有www.cnn.com下的子域名在系统中连续存放。这一行数据包含两个列族:"contents"和"anchor"。其中,列族"anchor"又包含两个列,qualifier分别为"cnnsi.com"和"my:look.ca"。 Google的很多服务,比如Web检索和用户的个性化设置,都需要保存不同时间的数据,这些不同的数据版本必须通过时间戳来区分。t4、t5和t6表示保存了三个时间点获取的网页
失效的版本将会由Bigtable的垃圾回收机制自动删除
Bigtable构建在GFS之上,为文件系统增加一层分布式索引层。另外,Bigtable依赖Google的Chubby(即分布式锁服务)进行服务器选举及全局信息维护。
Bigtable将大表划分为大小在100~200MB的子表(tablet),每个子表对应一个连续的数据范围。
客户端程序库(Client):提供Bigtable到应用程序的接口,应用程序通过客户端程序库对表格的数据单元进行增、删、查、改等操作。客户端通过Chubby锁服务获取一些控制信息,但所有表格的数据内容都在客户端与子表服务器之间直接传送;
主控服务器(Master):管理所有的子表服务器,包括分配子表给子表服务器,指导子表服务器实现子表的合并,接受来自子表服务器的子表分裂消息,监控子表服务器,在子表服务器之间进行负载均衡并实现子表服务器的故障恢复等。
Chubby是一个分布式锁服务,底层的核心算法为Paxos。Paxos算法的实现过程需要一个“多数派”就某个值达成一致,进而才能得到一个分布式一致性状态。也就是说,只要一半以上的节点不发生故障,Chubby就能够正常提供服务。Chubby服务部署在多个数据中心,典型的部署为两地三数据中心五副本,同城的两个数据中心分别部署两个副本,异地的数据中心部署一个副本,任何一个数据中心整体发生故障都不影响正常服务
Bigtable系统保证强一致性
Bigtable中Master对Tablet Server的监控是通过Chubby完成的,Tablet Server在初始化时都会从Chubby中获取一个独占锁
由于Bigtable负载均衡的过程中会停一会读写服务,负载均衡策略不应当过于激进。
随着数据不断写入和删除,某些子表可能太大,某些子表可能太小,需要执行子表分裂与合并操作。顺序分布与哈希分布的区别在于哈希分布往往是静态的,而顺序分布是动态的,需要通过分裂与合并操作动态调整。
Bigtable采用Merge-dump存储引擎。数据写入时需要先写操作日志,成功后应用到内存中的MemTable中,写操作日志是往磁盘中的日志文件追加数据,很好地利用了磁盘设备的特性。当内存中的MemTable达到一定大小,需要将MemTable转储(Dump)到磁盘中生成SSTable文件。由于数据同时存在MemTable和可能多个SSTable中,读取操作需要按从旧到新的时间顺序合并SSTable和内存中的MemTable数据。数据在SSTable中连续存放,因此可以同时满足随机读取和顺序读取两种需求。为了防止磁盘中的SSTable文件过多,需要定时将多个SSTable通过compaction过程合并为一个SSTable,从而减少后续读操作需要读取的文件个数。
插入、删除、更新、增加(Add)等操作在Merge-dump引擎中都看成一回事,除了最早生成的SSTable外,SSTable中记录的只是操作,而不是最终的结果,需要等到读取(随机或者顺序)时才合并得到最终结果。
数据在SSTable中按照主键有序存储
Tablet Server的缓存包括两种:块缓存(Block Cache)和行缓存(Row Cache)。其中,块缓存的单位为SSTable中的数据块,行缓存的单位为一行记录。随机读取时,首先查找行缓存;如果行缓存不命中,接着再查找块缓存。
Compaction后生成新的SSTable,原有的SSTable成为垃圾需要被回收掉
Master定期执行垃圾回收任务,这是一个标记删除(mark-and-sweep)过程
GFS+Bigtable两层架构以一种很优雅的方式兼顾系统的强一致性和可用性。底层文件系统GFS是弱一致性系统,可用性和性能很好,但是多客户端追加可能出现重复记录等数据不一致问题;上层的表格系统Bigtable通过多级分布式索引的方式使得系统对外整体表现为强一致性。
单副本服务。Bigtable架构非常适合离线或者半线上应用,然而,Tablet Server节点出现故障时部分数据短时间内无法提供读写服务,不适合实时性要求特别高的业务,如交易类业务。
Google Bigtable架构把可扩展性基本做到了极致,Megastore则是在Bigtable系统之上提供友好的数据库功能支持,增强易用性。Megastore是介于传统的关系型数据库和NoSQL之间的存储技术,它在Google内部使用广泛,如Google App Engine、社交类应用等。
最新读取和快照读取利用了Bigtable存储多版本数据的特性,保证不会读到未提交的事务。非一致性读取忽略日志的状态而直接读取Bigtable内存中最新的值,可能读到不完整的事务。
Megastore事务中的写操作采用了预写式日志(Write-ahead日志或REDO日志),也就是说,只有当所有的操作都在日志中记录下来后,写操作才会对数据执行修改。
Paxos协议使用了乐观锁的机制:尽管可能有多个写操作同时试图写同一个日志位置,但最后只有一个会成功。其他失败的写操作都会观察到成功的写操作,然后中止并重试。
假设事务T1和T2对同一个实体组并发执行,T1执行时读取a和b,T2读取a和d,接着T1和T2同时提交。Paxos协议保证T1和T2中有且只有一个事务提交成功,假如T1提交成功,T2将重新读取a和d后再次通过Paxos协议提交
对于多个集群之间的操作日志同步,Megastore系统采用的是基于Paxos的复制协议机制,对于普通的Master-Slave强同步机制,Master宕机后,Slave如果需要切换为Master继续提供服务需要首先确认Master宕机,检测Master宕机这段时间是需要停止写服务的,否则将造成数据不一致。
分布式数据库
有很多思路可以实现关系数据库的可扩展性。例如,在应用层划分数据,将不同的数据分片划分到不同的关系数据库上,如MySQL Sharding;或者在关系数据库内部支持数据自动分片,如Microsoft SQL Azure;或者干脆从存储引擎开始重写一个全新的分布式数据库,如Google Spanner以及Alibaba OceanBase。
为了扩展关系数据库,最简单也是最为常见的做法就是应用层按照规则将数据拆分为多个分片,分布到多个数据库节点,并引入一个中间层来对应用屏蔽后端的数据库拆分细节。
以MySQL Sharding架构为例,分为几个部分:中间层dbproxy集群、数据库组、元数据服务器、常驻进程。
应用程序通过MySQL原生的客户端与系统交互,支持JDBC,原有的单机访问数据库程序可以无缝迁移。
中间层解析客户端SQL请求并转发到后端的数据库。具体来讲,它解析MySQL协议,执行SQL路由,SQL过滤,读写分离,结果归并,排序以及分组,等等。
主机负责所有的写事务及强一致读事务,备机可以支持有一定延迟的读事务。
元数据服务器主要负责维护dbgroup拆分规则并用于dbgroup选主。dbproxy通过元数据服务器获取拆分规则从而确定SQL语句的执行计划。另外,如果dbgroup的主机出现故障,需要通过元数据服务器选主。元数据服务器本身也需要多个副本实现HA,一种常见的方式是采用Zookeeper实现。
部署在每台数据库服务器上的常驻进程,用于实现监控,单点切换,安装,卸载程序等。dbgroup中的数据库需要进行主备切换,软件升级等,这些控制逻辑需要与数据库读写事务处理逻辑隔离开来。假设数据库按照用户哈希分区,同一个用户的数据分布在一个数据库组上。如果SQL请求只涉及同一个用户(这对于大多数应用都是成立的),那么,中间层将请求转发给相应的数据库组,等待返回结果并将结果返回给客户端;如果SQL请求涉及多个用户,那么中间层需要转发给多个数据库组,等待返回结果并将结果执行合并、分组、排序等操作后返回客户端。由于中间层的协议与MySQL兼容,客户端完全感受不到与访问单台MySQL机器之间的差别。
引入数据库中间层将后端分库分表对应用透明化在大型互联网公司内部很常见。
数据库复制:MySQL主备之间只支持异步复制,而且主库压力较大时可能产生很大的延迟,因此,主备切换可能会丢失最后一部分更新事务,这时往往需要人工介入。
Microsoft SQL Azure是微软的云关系型数据库,后端存储又称为云SQL Server(Cloud SQL Server)。它构建在SQL Server之上,通过分布式技术提升传统关系型数据库的可扩展性和容错能力。
Google Spanner是Google的全球级分布式数据库(Globally-Distributed Database)。Spanner的扩展性达到了全球级,可以扩展到数百个数据中心,数百万台机器,上万亿行记录。更为重要的是,除了夸张的可扩展性之外,它还能通过同步复制和多版本控制来满足外部一致性,支持跨数据中心事务。
Spanner构建在Google下一代分布式文件系统Colossus之上。Colossus是GFS的延续,相比GFS,Colossus的主要改进点在于实时性,并且支持海量小文件。
正常情况下,这个主副本会在快要到期的时候将自己再次选为主副本;如果出现异常,例如主副本所在的spanserver宕机,其他副本会在10秒后通过Paxos协议选举为新的主副本。
Google的分布式存储系统一步步地从Bigtable到Megastore,再到Spanner,这也印证了分布式技术和传统关系数据库技术融合的必然性,即底层通过分布式技术实现可扩展性,上层通过关系数据库的模型和接口将系统的功能暴露给用户。阿里巴巴的OceanBase系统在设计之初就考虑到这两种技术融合的必然性,因此,一开始就将系统的最终目标定为:可扩展的关系数据库。
Amazon Web Services(AWS)是Amazon构建的一个云计算平台的总称,它提供了一系列云服务。通过这些服务,用户能否访问和使用Amazon的存储和计算基础设施。
1、计算类:AWS核心产品为弹性计算云EC2(Elastic Computing)。EC2几乎可以认为是迄今为止云计算领域最为成功的产品,通俗地讲,就是提供虚拟机,用户的应用程序部署在EC2实例中。EC2架构的核心是弹性伸缩,当托管的应用程序访问量变化时能够自动增加或者减少EC2实例,并通过弹性负载均衡技术将访问请求分发到新增的EC2实例上。在计费模式上,EC2按照使用量计费,而不是采用传统的预付费方式
EBS(Elastic Block Store)是一个分布式块设备,可以像本地的磁盘一样直接挂载在EC2实例上,与本地磁盘不同的是,保存到EBS的数据会由EBS的管理节点自动复制到多个存储节点上。EC2实例的本地存储是不可靠的,如果EC2实例出现故障,本地存储上保存的数据将会丢失,而保存到EBS上的数据不会丢失。EBS用于替代EC2实例的本地存储,从而增强EC2可靠性。
2、存储类:存储类产品较多,包括简单对象存储S3,表格存储系统SimpleDB、 DynamoDB、分布式关系数据库服务(Relational Datastore Service,RDS)以及简单消息存储(Simple Queue Service,SQS)
为了提高访问性能,S3中的对象还能够通过CloudFront缓存到不同地理位置的内容分发网络(Content Delivery Network,CDN)节点。SimpleDB和DynamoDB是分布式表格系统,支持对一张表格进行读写操作;RDS是分布式数据库,目前支持MySQL以及Oracle两种数据库。
3、工具集中包含各种语言的SDK、程序自动部署以及各种管理工具。另外,AWS通过CloudWatch系统提供丰富的监控功能。
假设网站MyWebSite.com托管在AWS平台的某个可用区域中。AWS开发者将Web应用上传到AWS平台并部署到指定的EC2实例上。EC2实例一般分成多个自动扩展组(Auto Scaling Group),并通过弹性负载均衡(Elastic Load Balancing)技术将访问请求自动分发到自动扩展组内的EC2实例。开发者的Web应用可以使用AWS平台上的存储类服务,包括S3、SimpleDB、DynamoDB、RDS以及SQS。
网站上往往有一些大对象,比如图片、视频,这些大对象存储在S3系统中,并通过内容分发技术缓存到多个CloudFront节点。当Internet用户浏览MyWebSite.com时,可能会请求S3中的大对象,这样的请求将通过DNS按照一定的策略定位到CloudFront节点。CloudFront首先在本地缓存节点查找对象,如果不存在,将请求源站获取S3中存储的对象数据,这一步操作称为回源。
Google云平台(Google App Engine,GAE)是一种PaaS服务,使得外部开发者可以通过Google期望的方式使用它的基础设施服务,目前支持Python和Java两种语言。GAE尤其适用于企业构建自己的企业私有云。
GAE云平台主要包含如下几个部分:
1、前端服务器。前端的功能包括负载均衡以及路由。前端服务器将静态内容请求转发到静态文件服务器,将动态内容请求转发到应用服务器。
2、应用服务器。应用服务器装载应用的代码并处理接收到的动态内容请求。
3、应用管理节点(App Master)。调度应用服务器,将应用服务器的变化通知前端,从而前端可以将访问流量切换到正确的应用服务器。
4、存储区。包括DataStore、MemCache以及BlobStore三个部分。应用的持久化数据主要存储在DataStore中,MemCache用于缓存,BlobStore是DataStore的一种补充,用于存储大对象。
5、服务区。除了必备的应用服务器以及存储区之外,GAE还包含很多服务,比如图像处理服务(Images)、邮件服务、抓取服务(URL fetch)、任务队列(Task Queue)以及用户服务(Users)等。
6、管理工具。GAE提供Web管理工具用于管理应用并监控应用的运行状态,比如资源消耗、应用日志等。
GAE的核心组件为应用服务器以及存储区,其中,应用服务器用于托管GAE平台用户的应用程序,存储区提供云存储服务
GAE对外不提供虚拟机服务,因此,对于不同的开发语言,需要提供不同的应用服务器实现,目前支持Python和Java两种语言
从托管Web应用程序的角度看,云平台主要包括云存储以及应用运行平台,
云存储组件包括两层:分布式存储层以及存储访问层。分布式存储层管理存储服务器集群,实现各个存储设备之间的协同工作,保证数据可靠性,对外屏蔽数据所在位置,数据迁移,数据复制,机器增减等变化,使得整个分布式系统看起来像是一台服务器。分布式存储层是云存储系统的核心,也是整个云存储平台最难实现的部分。CDN节点将云存储系统中的热点数据缓存到离用户最近的位置,从而减少用户的访问延时并节约带宽。
应用运行平台的主体为计算实例,计算实例最主要的功能有两个:开发者的应用程序运行环境以及离线任务处理。不同的云计算平台厂商的计算实例形式往往不同:AWS(Amazon Web Service)平台中的计算实例为Amazon的弹性计算(Elastic Computing,EC2)虚拟机,它们既用于托管开发者的Web程序,又可用来执行Hadoop MapReduce计算或者图像以及视频转换等离线任务;GAE(Google App Engine)平台中的计算实例分为前端实例(Frontend Instance)以及后端实例(Backend Instance),其中,前端实例为GAE特有的Python、Java以及Go语言运行容器,用于托管开发者使用Python、Java或者Go语言开发的Web程序,后端实例执行运行时间较长的离线任务;
云存储平台还包含一些公共服务,这些基础服务由云存储组件及运行平台组件所共用,如:
1、消息服务。消息服务将执行流程异步化,用于应用程序解耦
2、缓存服务。缓存服务用于存储云存储系统中的读多写少的热点数据,大多数云存储平台提供Memcache服务
3、用户管理。用户管理主要功能是用户身份认证,确保用户的身份合法,并存储用户相关的个人信息。云计算平台一般支持单点登录,在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的系统。
4、权限管理。为多个服务提供集中的权限控制,以确保应用和数据只能被有授权的用户访问。
5、安全服务。SQL注入漏洞、XSS跨站脚本漏洞。主机入侵检测通过主机日志安全分析,实时侦测系统密码破解,异常IP登录等攻击行为并实时报警;DDos缓解技术能够抵御SYN flood以及其他拒绝服务攻击。
6、计费管理。计算出用户的使用费用,并提供完善和详细的报表。云存储系统计费涉及的参数一般包括:CPU时间,网络出口带宽,存储量以及服务调用次数(包括读写API调用次数)。
7、资源管理。管理云存储平台中的所有服务器资源,将应用程序或者虚拟机映射自动部署到合适的计算实例,另外,自动调整计算实例的数量来帮助运行于其上的应用更好地应对突发流量。当计算实例发生故障时,资源管理系统还需要通知前端的负载均衡层,将流量切换到其他计算实例。
8、运维管理。云存储平台的运维需要做到自动化,从而降低运维成本
NoSQL存储系统则百花齐放,常见的NoSQL系统包括仅支持根据主键进行CRUD(Create,Read,Update,Delete)操作的键值(Key-Value)存储系统,也有基于传统的B树或者LSM树(Log-Structured Merge Tree)的存储系统。
CDN以及P2P技术将云存储系统中的热点数据缓存到离用户较近的边缘节点或者临近的其他用户的客户端,从而起到访问加速的作用,并且节省云存储服务提供商的网络带宽成本。
云存储系统通过存储访问层被个人用户的终端设备直接访问,或者被云存储平台中托管的应用程序访问。云存储访问层的功能包括:Web服务、负载均衡、安全服务以及计费。云存储系统对外提供统一的访问接口,常见的接口是REST或者SOAP这样的Web服务,需要通过Apache或者Nginx这样的Web服务器进行协议转化,Web服务器前端经常使用LVS(Linux Virtual Server)、HaProxy这样的软件或者专业的负载均衡设备(如F5负载均衡器)进行负载均衡。存储访问层需要提供安全和计费服务,安全服务包括身份认证、访问授权、综合防护、安全审计、DDos攻击预防/防火墙等。
基于虚拟机的弹性计算平台的优势在于兼容性,支持各种编程语言和平台。
云引擎。典型的云引擎为Google App Engine,底层设计的涉及的技术主要是应用容器(比如Java Tomcat、Jetty,Python Runtime)以及应用容器自动伸缩。当应用的负载过高时,自动增加应用的运行容器数;反之,自动减少应用的运行容器数。
提到大数据,首先想到的就是MapReduce,很多人甚至将大数据与MapReduce画等号。虽然MapReduce解决了海量数据离线分析问题,但是,随着应用对数据的实时性要求越来越高,流式计算系统和实时分析系统得到越来越广泛的应用。
数据的爆发式的增长,有一个趋势叫新摩尔定律。根据IDC作出的预测,数据一直都在以每年50%的速度增长,也就是说每两年增加一倍,这意味着人类在最近两年产生的数据量相当于之前产生的全部数据量。
从各种各样类型的数据,包括非结构化数据、半结构化数据以及结构化数据中,快速获取有价值信息的能力,就是大数据技术。
一提到大数据,大部分人首先想到的就是Hadoop。Hadoop是Google GFS以及MapReduce系统的开源实现,用户可以在不了解分布式底层细节的情况下开发分布式程序。它提供了离线处理功能,但无法做到动态和实时的分析。为了解决实时性问题,流计算和实时分析系统应运而生。
大数据技术进一步从海量数据中抽取数据的价值,从而诞生Google搜索引擎、Amazon商品推荐系统。
提到大数据,大多数人首先想到的就是MapReduce。MapReduce使得普通程序员可以在不了解分布式底层细节的前提下开发分布式程序。使用者只需编写两个称为Map和Reduce的函数即可,MapReduce框架会自动处理数据划分、多机并行执行、任务之间的协调,并且能够处理某个任务执行失败或者机器出现故障的情况。
MapReduce框架包含三种角色:主控进程(Master)用于执行任务划分、调度、任务之间的协调等;Map工作进程(Map Worker,简称Map进程)以及Reduce工作进程(Reduce Worker,简称Reduce进程)分别用于执行Map任务和Reduce任务。
MapReduce框架实现时主要做了两点优化:
1、本地化:尽量将任务分配给离输入文件最近的Map进程,如同一台机器或者同一个机架。通过本地化策略,能够大大减少传输的数据量。
2、备份任务:如果某个Map或者Reduce任务执行的时间较长,主控进程会生成一个该任务的备份并分配给另外一个空闲的Map或者Reduce进程。在大集群环境下,即使所有机器的配置相同,机器的负载不同也会导致处理能力相差很大,通过备份任务减少“拖后腿”的任务,从而降低整个作业的总体执行时间。
MapReduce框架有效地解决了海量数据的离线批处理问题,引发了一系列的扩展和改进。这些扩展包括:Google Tenzing、Google Pregel。
Google Tenzing是一个构建在MapReduce之上的SQL执行引擎,支持SQL查询且能够扩展到成千上万台机器,极大地方便了数据分析人员。
- 查询服务器(Query Server):作为连接客户端和worker池的中间桥梁而存在。查询服务器会解析客户端发送的查询请求,进行SQL优化,然后将执行计划发送给分布式Worker池执行。
- 分布式Worker池:作为执行系统,它会根据查询服务器生成的执行计划运行MapReduce任务。Worker池包含master和worker两种节点,其中,master对应MapReduce框架中的master进程,worker对应MapReduce框架中的map和reduce进程。
- 查询流程 1)用户通过Web UI、CLI或者API向查询服务器提交查询。 2)查询服务器将查询请求解析为一个中间语法树。 3)查询服务器从元数据服务器获取相应的元数据,然后创建一个更加完整的中间格式。 4)优化器扫描该中间格式进行各种优化,生成物理查询计划。 5)优化后的物理查询计划由一个或多个MapReduce作业组成。对于每个MapReduce作业,查询服务器通过master监听者找到一个可用的master,master将该作业划分为多个任务。 6)空闲的worker从master拉取已就绪的任务。Reduce进程会将它们的结果写入到一个中间存储区域中。 7)查询服务器监控这些中间存储区域,收集中间结果,并流失地返回给客户端。
- 查询服务器负责将用户的SQL操作转化为MapReduce作业
Google Pregel用于图模型迭代计算,图中的每个节点对应一个任务,每个图节点会产生输出消息给图中与它关联的后续节点,而每个节点会对从其他节点传入的输入消息进行处理。
流式计算同时具有存储系统和计算系统的特点,经常应用在一些类似反作弊、交易异常监控等场景。
源数据写入到流处理节点,流处理节点内部运行用户自定义的钩子函数对输入流进行处理,处理完后根据一定的规则转发给下游的流处理节点继续处理。
典型的钩子函数包括:聚合函数:计算最近一段时间窗口内数据的聚合值,如max、min、avg、sum、 count等。
流处理节点可以通过主备同步(Master/Slave)的方式容错,即将数据强同步到备机,如果主机出现故障,备机自动切换为主机继续提供服务。然而,这种方式的代价很高,且流式处理系统往往对错误有一定的容忍度,实际应用时经常选择其他代价更低的容错方式。
Yahoo S4最初是Yahoo为了提高搜索广告有效点击率而开发的一个流式处理系统。S4的主要设计目标是提供一种简单的编程接口来处理数据流,使得用户可以定制流式计算的操作算子。在容错设计上,S4做得比较简单:一旦S4集群中的某个节点故障,会自动切换到另外一个备用节点,但是原节点的内存状态将丢失。
S4中每个流处理节点称为一个处理节点(Processing Node,PN),其主要工作是监听事件,当事件到达时调用合适的处理元(Processing Elements,PE)处理事件。
事件监听器(Event Listener)负责监听事件并转交给PE容器(Processing Element Container,PEC),由PEC交给合适的PE处理业务逻辑。配置文件中会配置PE原型(PE prototype),包括其功能、处理的事件类型(event type)、关心的key以及关心的key值。每个PE只负责处理自己所关心的事件,也就是说,只有当事件类型、key类型和key值都匹配时,才会交由该PE进行计算处理。PE处理完逻辑后根据其定义的输出方法可以输出事件,事件交由分发器(Dispatcher)与通信层(Communication Layer)进行交互并由输出器(Emitter)输出至下一个逻辑节点。
通信层提供集群路由(Routing)、负载均衡(Load Balancing)、故障恢复管理(Failover Management)、逻辑节点到物理节点的映射(存放在Zookeeper上)。当检测到节点故障时,会切换到备用节点,并自动更新映射关系。通信层隐藏的映射使得PN发送消息时只需要关心逻辑节点而不用关心物理节点。
Twitter Storm是目前广泛使用的流式计算系统,它创造性地引入了一种记录级容错的方法。
海量数据离线分析对于MapReduce这样的批处理系统挑战并不大,如果要求实时,又分为两种情况:如果查询模式单一,那么,可以通过MapReduce预处理后将最终结果导入到在线系统提供实时查询;如果查询模式复杂,例如涉及多个列任意组合查询,那么,只能通过实时分析系统解决。实时分析系统融合了并行数据库和云计算这两类技术,能够从海量数据中快速分析出汇总结果。
并行数据库往往采用MPP(Massively Parallel Processing,大规模并行处理)架构。MPP架构是一种不共享的结构,每个节点可以运行自己的操作系统、数据库等。每个节点内的CPU不能访问另一个节点的内存,节点之间的信息交互是通过节点互联网络实现的。
将数据分布到多个节点,每个节点扫描本地数据,并由Merge操作符执行结果汇总。
Merge操作符:系统中存在一个或者多个合并节点,它会发送命令给各个数据分片请求相应的数据,每个数据分片所在的节点扫描本地数据,排序后回复合并节点,由合并节点通过merge操作符执行数据汇总。Merge操作符是一个统称,涉及的操作可能是limit、order by、group by、join等。
如果Merge节点处理的数据量特别大,可以通过Split操作符将数据划分到多个节点,每个节点对一部分数据执行group by、join等操作后再合并最终结果。如图13-10,假如需要执行"select*from A,B where A.x=B.y",可以分别根据A.x和B.x的哈希值将表A和B划分为A0、A1以及B0、B1。由两个节点分别对A0、B0以及A1、B1执行join操作后再合并join结果。
Greenplum是EMC公司研发的一款采用MPP架构的OLAP产品,底层基于开源的PostgreSQL数据库。
Vertica在架构上与OceanBase有相似之处。
Google Dremel是Google的实时分析系统,可以扩展到上千台机器规模,处理PB级别的数据。
Dremel系统融合了并行数据库和Web搜索技术。首先,它借鉴了Web搜索中的“查询树”的概念,将一个巨大复杂的查询,分割成大量较小的查询,使其能并发地在大量节点上执行。其次,和并行数据库类似,Dremel提供了一个SQL-like的接口,且支持列式存储。
Dremel与MapReduce的比较 MapReduce的输出结果直接由reduce任务写入到分布式文件系统,因此,只要reduce任务个数足够多,输出结果可以很大;而Dremel中的最终数据汇聚到一个根节点,因此一般要求最终的结果集比较小,例如GB级别以下。 Dremel的优势在于实时性,只要服务器个数足够多,大部分情况下能够在3秒以内处理完成TB级别数据。