第1章 概述
1.1 分布式存储概念
分布式存储系统的特性:
分布式存储系统的挑战:
- 数据分布
- 一致性
- 容错
- 负载均衡
- 事务与并发控制
- 易用性
- 压缩/解压缩
1.2 分布式存储分类
- 非结构化数据,如办公文档、文本、图片、图像、音频、视频信息等。
- 结构化数据,如关系数据库
- 半结构化数据,如HTML文档
本书将分布式存储系统分为四类:
- 分布式文件系统,存储图片、视频等非结构化数据对象,通常称为Blob(Binary Large Object,二进制大对象)数据。典型系统如facebook Haystack、Taobao File System(TFS)。整体上看,分布式文件系统存储三种类型的数据:Blob对象、定长块以及大文件。
- 分布式键值系统,存储关系简单的半结构化数据,典型系统有Amazon Dynamo以及Taobao Tair,通常用做缓存。
- 分布式表格系统,存储关系较为复杂的半结构化数据,典型系统包括Google Bigtable以及Megastore,Microsoft Azure Table Storage。主要支持针对单张表格的操做,不支持如多表关联、嵌套子查询等。
- 分布式数据库,存储结构化数据,提供SQL关系查询语言,支持多表关联、嵌套子查询等复杂操做,并提供数据库事务以及并发控制。典型系统包括MySQL数据库分片(MySQL Sharding)集群,Amazon RDS以及Microsoft SQL Azure。
第2章 单机存储系统
2.1 硬件基础
2.1.1 CPU架构
经典的多CPU架构为对称多处理结构(Symmetric Multi-Processing, SMP),为提升可扩展性,如今主流服务器架构通常为NUMA(Non-Uniform Memory Access,非一致存储访问)架构。
2.1.2 IO总线
以Intel x48为例,是典型的南、北桥架构。
2.1.3 网络拓扑
分为传统的数据中心网络拓扑,Google在2008年将网络改造为扁平化拓扑结构,即三级CLOS网络。
2.1.4 性能参数
存储系统的性能瓶颈主要在于磁盘随机读写。设计存储引擎的时候会针对磁盘的特性作不少的处理,好比将随机写操做转化为顺序写,经过缓存减小磁盘随机读操做。
固态磁盘(SSD)特色是随机读取延迟小,可以提供很高的IOPS(每秒读写,Input/Output Per Second)性能。它的主要问题在于容量和价格,设计存储系统的时候通常能够用来作缓存或者性能要求较高的关键业务。
2.1.5 存储层次架构
存储系统的性能主要包括两个维度:吞吐量以及访问延时,设计系统时要求可以在保证访问延时的基础上,经过最低的成本实现尽量高的吞吐量。磁盘和SSD的访问延时差异很大,但带宽差异不大,所以磁盘适合大块顺序访问的存储系统,SSD适合随机访问较多或者对延时比较敏感的关键系统。两者也经常组合在一块儿进行混合存储,热数据(访问频繁)存储到SSD中,冷数据(访问不频繁)存储到磁盘中。
2.2 单机存储引擎
存储系统的基本功能包括:增、删、读、改,其中,读取操做又分为随机读取和顺序读取。哈希存储引擎是哈希表的持久化实现,支持增、删、改,以及随机读取操做,但不支持顺序扫描,对应的存储系统为键值(Key-Value)存储系统;B树(B-Tree)存储引擎是B树的持久化实现,不只支持单条记录的增、删、读、改操做,还支持顺序扫描,对应的存储系统是关系数据库。固然,键值系统也能够经过B树存储引擎实现;LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎同样,支持增、删、改、随机读取以及顺序扫描。它经过批量转储技术规避磁盘随机写入问题,普遍应用于互联网的后台存储系统,例如Google Bigtable, Google LevelDB以及Facebook开源的Cassandra系统。
2.2.1 哈希存储引擎
Bitcask是一个基于哈希表结构的键值存储系统,它仅支持追加操做(Append-only)。
1 数据结构
内存中采用基于哈希表的索引数据结构,哈希表的做用是经过主键快速地定位到value的位置。
Bitcask在内存中存储了主键和value的索引信息,磁盘文件中存储了主键和value的实际内容。
2 按期合并
3 快速恢复
Bitcask经过索引文件(hint file)来提升重建哈希表的速度。简单来讲,索引文件就是将内存中的哈希索引表转储到磁盘生成的结果文件。
2.2.2 B树存储引擎
1 数据结构
MySQL InnoDB按照页面(Page)来组织数据,每一个页面对应B+树的一个节点。其中,叶子节点保存每行的完整数据,非叶子节点保存索引信息。数据在每一个节点中有序存储,数据库查询时须要从根节点开始二分查找直到叶子节点,每次读取一个节点,若是对应的页面不在内存中,须要从磁盘中读取并缓存起来。B+树的根节点是常驻内存的,所以,B+树一次检索最多须要h-1次磁盘IO,复杂度为O(h)=O(logd^N)(N为元素个数,d为每一个节点的出席,h为B+树高度)。修改操做首先须要记录提交日志,接着修改内存中的B+树。若是内存中的被修改过的页面超过必定的比率,后台线程会将这些页面刷到磁盘中持久化。
2 缓冲区管理
缓冲区管理器负责将可用的内存划分红缓冲区,缓冲区是与页面同等大小的区域,磁盘块的内容能够传送到缓冲区中。缓冲区管理器的关键在于替换策略,即选择将哪些页面淘汰出缓冲池。常见的算法有如下两种。
(1)LRU,LRU算法淘汰最长时间没有读或者写过的块。
(2)LIRS,解决全表扫描污染缓冲池的问题。现代数据库通常采用LIRS算法,将缓冲池分为两级,数据首先进入第一级,若是数据在较短的时间内被访问两次或者以上,则成为热点数据进入第二级,每一级内部仍是采用LRU替换算法。
2.2.3 LSM树存储引擎
LSM树(Log Structured Merge Tree)的思想很是朴素,就是将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操做批量写入磁盘,读取时须要合并磁盘中的历史数据和内存中最近的修改操做。
1 存储结构
LevelDB存储引擎主要包括:内存中的MemTable和不可变MemTable(Immutable MemTable, 也称为Frozen MemTable,即冻结MemTable)以及磁盘上的几种主要文件:当前(Current)文件、清单(Manifest)文件、操做日志(Commit Log, 也称为提交日志)文件以及SSTable文件。当应用写入一条记录时,LevelDB会首先将修改操做写入到操做日志文件,成功后再将修改操做应用到MemTable,这样就完成了写入操做。
当MemTable占用的内存达到一个上限值后,须要将内存的数据转储到外存文件中。
SSTable中的文件是按照记录的主键排序的,每一个文件有最小 的主键和最大的主键。
2 合并
LevelDB的Compaction操做分为两种:minor compaction和major compaction。minor compaction是内存中的MemTable转储到SSTable。major compaction是合并多个SSTable文件。
2.3 数据模型
2.3.1 文件模型算法
POSIX(Portable Operation System Interface)是应用程序访问文件系统的API标准,它定义了文件系统存储接口及操做集。
2.3.2 关系模型数据库
每一个关系是一个表格,由多个元组(行)构成,而每一个元组又包含多个属性(列)。关系名、属性名以及属性类型称做该关系的模式(schema)。
数据库语言SQL用于描述查询以及修改操做。
2.3.3 键值模型缓存
大量的NoSQL系统采用了键值模型(也称为Key-Value模型),每行记录由主键和值两个部分组成,支持基于主键的以下操做:Put, Get, Delete
NoSQL系统中使用比较普遍的模型是表格模型。表格模式弱化了关系模型中的多表关联,支持基于单表的简单操做,典型的系统是Google Bigtable以及其开源Java实现HBase。主要操做以下:Insert, Delete, Update, Get, Scan。
2.3.4 SQL与NoSQL服务器
关系数据库在海量数据场景面临以下挑战:
NoSQL系统面临以下问题:
2.4 事务与并发控制
2.4.1 事务
事务的四个基本属性:
(1)原子性(Atomicity)
(2)一致性(Consistency)
(3)隔离性(Isolation)
(4)持久性(Durability)
SQL定义了4种隔离级别:
- Read Uncommitted(RU):读取未提交的数据
- Read Committed(RC):读取已提交的数据
- Repeatable Read(RR):可重复读取
- Serialization(S):可序列化,即数据库的事务是可串行化执行的。这是最高的隔离级别。
隔离级别的下降可能致使读到脏数据或者事务执行异常,例如:
- Lost Update(LU):丢失更新
- Dirty Reads(DR):脏读
- Non-Repeatable Reads(NRR):不可重复读
- Second Lost Updates problem(SLU): 第二类丢失更新
- Phantom Reads(PR): 幻读
2.4.2 并发控制
1 数据库锁
事务分为几种类型:读事务,写事务以及读写混合事务。相应地,锁也分为两种类型:读锁以及写锁,容许对同一个元素加多个读锁,但只容许加一个写锁,且写事务将阻塞读事务。
2 写时复制(Copy-On-Write,COW)
写时复制读操做不用加钞,极大地提升了读取性能。
图2-10中写时复制B+树执行写操做的步骤以下:
(1)拷贝:将从叶子到根节点路径上的全部节点拷贝出来。
(2)修改:对拷贝的节点执行修改。
(3)提交:原子地切换根节点的指针,使之指向新的根节点。
写时复制技术原理简单,问题是每次写操做都须要拷贝从叶子到根节点路径上的全部节点,写操做成本高,另外,多个写操做之间的互斥的,同一时刻只容许一个写操做。
3 多版本并发控制(MVCC,Multi-Version Concurrency Control)
也可以实现读事务不加锁。以MySQL InnoDB存储引擎为例,InnoDB对每一行维护了两个隐含的列,其中一列存储行被修改的"时间",另一列存储行被删除的"时间"。注意,InnoDB存储的并非绝对时间,而是与时间对应的数据库系统的版本号。
MVCC读取数据的时候不用加锁,每一个查询都经过版本检查,只得到本身须要的数据版本,从而大大提升了系统的并发度。
2.5 故障恢复
数据库系统以及其余的分布式存储系统通常采用操做日志(有时也称为提交日志,即Commit Log)技术来实现故障恢复。操做日志分为回滚日志(UNDO Log)、重作日志(REDO Log)以及UNDO/REDO日志。
2.5.1 操做日志
关系数据库系统通常采用UNDO/REDO日志。
2.5.2 重作日志
2.5.3 优化手段
1 成组提交(Group Commit)
2 检查点(Checkpoint)
须要将内存中的数据按期转储(Dump)到磁盘,这种技术称为checkpoint(检查点)技术。系统按期将内存中的操做以某种易于加载的形式(checkpoint文件)转储到磁盘中,并记录checkpoint时刻的日志回放点,之后故障恢复只须要回放checkpoint时刻的日志回放点以后的REDO日志。
2.6 数据压缩
2.6.1 压缩算法
压缩的本质就是找数据的重复或者规律,用尽可能少的字节表示。
1 Huffman编码
前缀编码要求一个字符的编码不能是另外一个字符的前缀。
2 LZ系列压缩算法
LZ系列压缩算法是基于字典的压缩算法。
3 BMDiff与Zippy
在Google的Bigtable系统中,设计了BMDiff和Zippy两种压缩算法。
2.6.2 列式存储
OLTP(Online Transaction Processing,联机事务处理)应用适合采用行式数据库。OLAP类型的查询可能要访问几百万甚至几十亿数据行,且该查询每每只关心少数几个数据列,列式数据库能够大大提升OLAP大数据量查询的效率。列组(column group)是一种行列混合存储械,这种模式可以同时知足OLTP和OLAP的查询需求。
因为同一个数据列的数据重复度很高,所以,列式数据库压缩时有很大的优点,如位图索引等。