NoSQL系统Word文档下载推荐.docx
《NoSQL系统Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《NoSQL系统Word文档下载推荐.docx(25页珍藏版)》请在冰豆网上搜索。
![NoSQL系统Word文档下载推荐.docx](https://file1.bdocx.com/fileroot1/2023-2/2/3ccb85e7-cbbf-4a29-b58e-245e1f33bdb8/3ccb85e7-cbbf-4a29-b58e-245e1f33bdb81.gif)
employeeid=3,或者salary>
$20000)。
更复杂一些的任务会让数据库做一些额外的工作,比如跨表的联合查询(例:
查出3号员的部门名称是什么)。
一些复杂的查询,比如统计操作(例:
算出所有员工的平均工资),甚至可能会导致全表扫描。
关联型的数据模型定义了高度结构化的数据结构,以及对这些结构之间关系的严格定义。
在这样的数据模型上执行的查询操作会比较局限,而且可能会导致复杂的数据遍历操作。
数据结构的复杂性及查询的复杂性,会导致系统产生如下的一些限制:
复杂导致不确定性。
使用SQL的一个问题就是计算某个查询的代价或者产生的负载几乎是不可能的。
使用简单的查询语言可能会导致应用层的逻辑更复杂,但是这样可以将存储系统的工作简单化,让它只需要响应一些简单的请求。
对一个问题建模有很多种方式。
其中关联型的数据模型是非常严格的一种:
表结构的定义规定了表中每一行数据的存储内容。
如果你的数据结构化并没有那么强,或者对每一行数据的要求比较灵活,那可能关联型的数据模型就太过严格了。
类似的,应用层的开发人员可能对关联型的数据结构并不满意。
比如很多应用程序是用面向对象的语言写的,数据在这些语言中通常是以列表、队列或集合的形式组织的,程序员们当然希望他们的数据存储层也能和应用层的数据模型一致。
当数据量增长到一台机器已经不能容纳,我们需要将不同的数据表分布到不同的机器。
而为了避免在不同机器上的数据表在进行联合查询时需要跨网络进行。
我们必须进行反范式的数据库设计,这种设计方式要求我们把需要一次性查询到的数据存储在一起。
这样做使得我们的系统变得就像一个主键查询系统一样,于是我们开始思考,是否有其它更适合我们数据的数据模型。
通常来说,舍弃多年以来的设计思路是不明智的。
当你要把数据存到数据库,当考虑到SQL与关联型的数据模型,这些都是数十年的研究的开发成果,提供丰富的数据模型,提供复杂操作的保证。
而当你的问题涉及到大数据量,高负载或者说你的数据结构在SQL与关联型数据模型下很难得到优化,NoSQL可能是更好的选择。
1.2.NoSQL的启示
NoSQL运动受到了很多相关研究论文的启示,这所有论文中,最核心的有两个。
Google的BigTable[CDG+06]提出了一种很有趣的数据模型,它将各列数据进行排序存储。
数据值按范围分布在多台机器,数据更新操作有严格的一致性保证。
Amazon的Dynamo[DHJ+07]使用的是另外一种分布式模型。
Dynamo的模型更简单,它将数据按key进行hash存储。
其数据分片模型有比较强的容灾性,因此它实现的是相对松散的弱一致性:
最终一致性。
接下来我们会深入介绍这些设计思想,而实际上在现实中这些思想经常是混搭使用的。
比如像HBase及其它一些NoSQL系统他们在设计上更接受BigTable的模型,而像Voldemort系统它就和Dynamo更像。
同时还有像Cassandra这种两种特性都具备的实现(它的数据模型和BigTable类似,分片策略和一致性机制和Dynamo类似)。
1.3.特性概述
NoSQL系统舍弃了一些SQL标准中的功能,取而代之的是提供了一些简单灵活的功能。
NoSQL的构建思想就是尽量简化数据操作,尽量让执行操作的效率可预知。
在很多NoSQL系统里,复杂的操作都是留给应用层来做的,这样的结果就是我们对数据层进行的操作得到简化,让操作效率可预知。
NoSQL系统不仅舍弃了很多关系数据库中的操作。
它还可能不具备关系数据库以下的一些特性:
比如通常银行系统中要求的事务保证,一致性保证以及数据可靠性的保证等。
事务机制提供了在执行多个命令时的all-or-nothing保证。
一致性保证了如果一个数据更新后,那么在其之后的操作中都能看到这个更新。
可靠性保证如果一个数据被更新,它就会被写到持久化的存储设备上(比如说磁盘),并且保证在数据库崩溃后数据可恢复。
通过放宽对上述几点特性的要求,NoSQL系统可以为一些非银行类的业务提供以性能换稳定的策略。
而同时,对这几点要求的放宽,又使得NoSQL系统能够轻松的实现分片策略,将远远超出单机容量的大量数据分布在多台机器上的。
由于NoSQL系统还处在萌芽阶段,本章中提到的很多NoSQL架构都是用于满足各种不同用户的需求的。
对这些架构进行总结不太可能,因为它们总在变化。
所以希望你能记住的一点是,不同的NoSQL系统的特点是不同的,通过本章的内容,希望你能该根据自己的业务情况来选择合适的NoSQL系统,而本章内容不可能给你直接的答案。
当你去考查一个NoSQL系统的时候,下面的的几点是值得注意的:
●数据模型及操作模型:
你的应用层数据模型是行、对象还是文档型的呢?
这个系统是否能支持你进行一些统计工作呢?
●可靠性:
当你更新数据时,新的数据是否立刻写到持久化存储中去了?
新的数据是否同步到多台机器上了?
●扩展性:
你的数据量有多大,单机是否能容下?
你的读写量求单机是否能支持?
●分区策略:
考虑到你对扩展性,可用性或者持久性的要求,你是否需要一份数据被存在多台机器上?
你是否需要知道数据在哪台机器上,以及你能否知道。
●一致性:
你的数据是否被复制到了多台机器上,这些分布在不同点的数据如何保证一致性?
●事务机制:
你的业务是否需要ACID的事务机制?
●单机性能:
如果你打算持久化的将数据存在磁盘上,哪种数据结构能满足你的需求(你的需求是读多还是写多)?
写操作是否会成为磁盘瓶颈?
●负载可评估:
对于一个读多写少的应用,诸如响应用户请求的web应用,我们总会花很多精力来关注负载情况。
你可能需要进行数据规模的监控,对多个用户的数据进行汇总统计。
你的应用场景是否需要这样的功能呢?
尽管后三点在本章没有独立的小节进行描述,但它们和其它几点一样重要,下面就让我们对以上的几点进行阐述。
2.NoSQL数据模型及操作模型
数据库的数据模型指的是数据在数据库中的组织方式,数据库的操作模型指的是存取这些数据的方式。
通常数据模型包括关系模型、键值模型以及各种图结构模型。
操作语言可能包括SQL、键值查询及MapReduce等。
NoSQL通常结合了多种数据模型和操作模型,提供了不一样的架构方式。
2.1.基于key值存储的NoSQL数据模型
在键值型系统中,复杂的联合操作以及满足多个条件的取数据操作就不那么容易了,需要我们创造性的建立和使用键名。
如果一个程序员既想通过员工号查到员工信息,又想通过部门号查到员工信息,那么他必须建立两种类型的键值对。
例如emplyee:
30这个键用于指向员工号为30的员工信息。
employee_departments:
20可能用到指向一个包含在20号部门工作的所有员工工号的列表。
这样数据库中的联合操作就被转换成业务层的逻辑了:
要获取部门号为20的所有员工的信息,应用层可以先获取employee_departments:
20这个列表,然后再循环地拿这个列表中的ID通过获取employee:
ID得到所有员工的信息。
键值查找的一个好处是,对数据库的操作模式是固定的,这些操作所产生的负载也是相对固定且可预知的。
这样像分析整个应用中的性能瓶颈这种事,就变得简单多了。
因为复杂的逻辑操作并不是放在数据库里面黑箱操作了。
不过这样做之后,业务逻辑和数据逻辑可能就没那么容易分清了。
下面我们快速地把各种键值模型的数据结构简单描述一下。
看看不同的NoSQL系统的在这方面的不同实现方式。
2.1.1.Key-Value存储
Key-Value存储可以说是最简单的NoSQL存储。
每个key值对应一个任意的数据值。
对NoSQL系统来说,这个任意的数据值是什么,它并不关心。
比如在员工信念数据库里,exployee:
30这个key对应的可能就是一段包含员工所有信息的二进制数据。
这个二进制的格式可能是ProtocolBuffer、Thrift或者Avro都无所谓。
如果你使用上面说的Key-Value存储来保存你的结构化数据,那么你就得在应用层来处理具体的数据结构:
单纯的Key-Value存储是不提供针对数据中特定的某个属性值来进行操作。
通常它只提供像set、get和delete这样的操作。
以Dynamo为原型的Voldemort数据库,就只提供了分布式的Key-Value存储功能。
BDB是一个提供Key-Value操作的持久化数据存储引擎。
2.1.2.Key–结构化数据存储
Key对应结构化数据存储,其典型代表是Redis,Redis将Key-Value存储的Value变成了结构化的数据类型。
Value的类型包括数字、字符串、列表、集合以及有序集合。
除了set/get/delete操作以为,Redis还提供了很多针对以上数据类型的特殊操作,比如针对数字可以执行增、减操作,对list可以执行push/pop操作,而这些对特定数据类型的特定操作并没有对性能造成多大的影响。
通过提供这种针对单个Value进行的特定类型的操作,Redis可以说实现了功能与性能的平衡。
2.1.3.Key–文档存储
Key–文档存储的代表有CouchDB、MongoDB和Riak。
这种存储方式下Key-Value的Value是结构化的文档,通常这些文档是被转换成JSON或者类似于JSON的结构进行存储。
文档可以存储列表,键值对以及层次结构复杂的文档。
MongoDB将Key按业务分到各个collection里,这样以collection作为命名空间,员工信息和部门信息的Key就被隔开了。
CouchDB和Riak把类型跟踪这种事留给了开发者去完成。
文档型存储的灵活性和复杂性是一把双刃剑:
一方面,开发者可以任意组织文档的结构,另一方面,应用层的查询需求会变得比较复杂。
2.1.4.BigTable的列簇式存储
HBase和Cassandra的数据模型都借鉴自Google的BigTable。
这种数据模型的特点是列式存储,每一行数据的各项被存储在不同的列中(这些列的集合称作列簇)。
而每一列中每一个数据都包含一个时间戳属性,这样列中的同一个数据项的多个版本都能保存下来。
列式存储可以理解成这样,将行ID、列簇号,列号以及时间戳一起,组成一个Key,然后将Value按Key的顺序进行存储。
Key值的结构化使这种数据结构能够实现一些特别的功能。
最常用的就是将一个数据的多个版本存成时间戳不同的几个值,这样就能很方便的保存历史数据。
这种结构也能天然地进行高效的松散列数据(在很多行中并没有某列的数据)存储。
当然,另一方面,对于那些很少有某一行有NULL值的列,由于每一个数据必须包含列标识,这又会造成空间的浪费。
这些NoSQL系统对BigTable数据模型的实现多少有些差别,这其中以Cassandra进行的变更最为显著。
Cassandra引入了超级列(supercolumn)的概念,通过将列组织到相应的超级列中,可以在更高层级上进行数据的组织,索引等。
这一做法也取代了localitygroups的概念(这一概念的实现可以让相关的几个行的数据存储在一起,以提高存取性能)。
2.2.图结构存储
图结构存储是NoSQL的另一种存储实现。
图结构存储的一个指导思想是:
数据并非对等的,关系型的存储或者键值对的存储,可能都不是最好的存储方式。
图结构是计算机科学的基础结构之一,Neo4j和HyperGraphDB是当前最流行的图结构数据库。
图结构的存储与我们之前讨论过的几种存储方式很不同,这种不同几乎体现在每一个方面,包括:
数据模型、数据查询方式、数据在磁盘上的组织方式、在多个结点上的分布方式,甚至包括对事务机制的实现等等。
由于篇幅的限制,对这些方面我们暂时不做深入的讨论,这里需要你了解的是,某些数据结构使用图结构的数据库进行存储可能会更好。
2.3.复杂查询
在NoSQL存储系统中,有很多比键值查找更复杂的操作。
比如MongoDB可以在任意数据行上建立索引,可以使用Javascript语法设定复杂的查询条件。
BigTable型的系统通常支持对单独某一行的数据进行遍历,允许对单列的数据进行按特定条件地筛选。
CouchDB允许你创建同一份数据的多个视图,通过运行MapReduce任务来实现一些更为复杂的查询或者更新操作。
很多NoSQL系统都支持与Hadoop或者其它一些MapReduce框架结合来进行一些大规模数据分析工作。
2.4.事务机制
传统的关系型数据库在功能支持上通常很宽泛,从简单的键值查询,到复杂的多表联合查询再到事务机制的支持。
而与之不同的是,NoSQL系统通常注重性能和扩展性,而非事务机制。
传统的SQL数据库的事务通常都是支持ACID的强事务机制。
A代表原子性,即在事务中执行多个操作是原子性的,要么事务中的操作全部执行,要么一个都不执行;
C代表一致性,即保证进行事务的过程中整个数据加的状态是一致的,不会出现数据花掉的情况;
I代表隔离性,即两个事务不会相互影响,覆盖彼此数据等;
D表示持久化,即事务一量完成,那么数据应该是被写到安全的,持久化存储的设备上(比如磁盘)。
ACID的支持使得应用者能够很清楚他们当前的数据状态。
即使如对于多个事务同时执行的情况下也能够保证数据状态的正常性。
但是如果要保证数据的一致性,通常多个事务是不可能交叉执行的,这样就导致了可能一个很简单的操作需要等等一个复杂操作完成才能进行的情况。
对很多NoSQL系统来说,对性能的考虑远在ACID的保证之上。
通常NoSQL系统仅提供对行级别的原子性保证,也就是说同时对同一个Key下的数据进行的两个操作,在实际执行的时候是会串行的执行,保证了每一个Key-Value对不会被破坏。
对绝大多数应用场景来说,这样的保证并不会引起多大的问题,但其换来的执行效率却是非常可观的。
当然,使用这样的系统可能需要我们在应用层的设计上多做容错性和修正机制的考虑。
在NoSQL中,Redis在事务支持这方面上不太一样,它提供了一个MULTI命令用来将多个命令进行组合式的操作,通过一个WATCH命令提供操作隔离性。
这和其它一些提供test-and-set这样的低层级的隔离机制类似。
2.5.Schema-free的存储
还有一个很多NoSQL都有的共同点,就是它通常并没有强制的数据结构约束。
即使是在文档型存储或者列式存储上,也不会要求某一个数据列在每一行数据上都必须存在。
这在非结构化数据的存储上更方便,同时也省去了修改表结构的代价。
而这一机制对应用层的容错性要求可能会更高。
比如程序可能得确定如果某一个员工的信息里缺少lastname这一项,是否算是错误。
或者说某个表结构的变更是否对所有数据行起了作用。
还有一个问题,就是数据库的表结构可能会因为项目的多次迭代而变得混乱不堪。
3.数据可靠性
最理想状态是,数据库会把所有写操作立刻写到持久化存储的设备,同时复制多个副本到不同地理位置的不同节点上以防止数据丢失。
但是这种对数据安全性的要求对性能是有影响的,所以不同的NoSQL系统在自身性能的考虑下,在数据安全上采取了不太一样的策略。
一种典型的出错情况是重启机器或者机器断电了,这时候需要让数据从内存转存到磁盘才能保证数据的安全性,因为磁盘数据不会因断电而丢失。
而要避免磁盘损坏这种故障,就需要将数据冗余的存在其它磁盘上,比如使用RAID来做镜像,或者将数据同步到不同机器。
但是有些时候针对同一个数据中心来说,是不可能做到完全的数据安全的,比如一旦发生飓风地震这种天灾,整个机房机器可能都会损坏,所以,在这种情况下要保证数据的安全性,就必须将数据备份存储到其它地理位置上比较远的数据中心。
所以,具体各个NoSQL系统在数据安全性和性能上的权衡策略也不太一样。
3.1.单机可靠性
单机可靠性理解起来非常简单,它的定义是写操作不会由于机器重启或者断电而丢失。
通常单机可靠性的保证是通过把数据写到磁盘来完成的,而这通常会造成磁盘IO成为整个系统的瓶颈。
而事实上,即使你的程序每次都把数据写到了磁盘,实际上由于操作系统buffer层的存在,数据还是不会立刻被写到物理磁盘上,只有当你调用fsync这个系统调用的时候,操作系统才会尽可能的把数据写到磁盘。
一般的磁盘大概能进行每秒100-200次的随机访问,每秒30-100MB的顺序写入速度。
而内存在这两方面的性能都有数量级上的提升。
通过尽量减少随机写,取而代之的对每个磁盘设备进行顺序写,这样能够减少单机可靠性保证的代价。
也就是说我们需要减少在两次fsync调用之间的写操作次数,而增加顺序写操作的数量。
下面我们说一下一些在单机可靠性的保证下提高性能的方法。
3.1.1.控制fsync的调用频率
Memcached是一个纯内存的存储,由于其不进行磁盘上的持久化存储,从而换来了很高的性能。
当然,在我们进行服务器重启或者服务器意外断电后,这些数据就全丢了。
因此Memcached是一个非常不错的缓存,但是做不了持久化存储。
Redis则提供了几种对fsync调用频率的控制方法。
应用开发者可以配置Redis在每次更新操作后都执行一次fsync,这样会比较安全,当然也就比较慢。
Redis也可以设置成N秒种调用一次fsync,这样性能会更好一点。
但这样的后果就是一旦出现故障,最多可能导致N秒内的数据丢失。
而对一些可靠性要求不太高的场合(比如仅仅把Redis当Cache用的时候),应用开发者甚至可以直接关掉fsync的调用:
让操作系统来决定什么时候需要把数据flush到磁盘。
(译者:
这只是Redisappendonlyfile的机制,Redis是可以关闭aof日志的,另外请注意:
Redis本身支持将内存中数据dump成rdb文件的机制,和上面说的不是一回事。
)
3.1.2.使用日志型的数据结构
像B+树这样的一些数据结构,使得NoSQL系统能够快速的定位到磁盘上的数据,但是通常这些数据结构的更新操作是随机写操作,如果你在每次操作后再调用一次fsync,那就会造成频繁的磁盘随机访问了。
为了避免产生这样的问题,像Cassandra、HBase、Redis和Riak都会把写操作顺序的写入到一个日志文件中。
相对于存储系统中的其它数据结构,上面说到的日志文件可以频繁的进行fsync操作。
这个日志文件记录了操作行为,可以用于在出现故障后恢复丢失那段时间的数据,这样就把随机写变成顺序写了。
虽然有的NoSQL系统,比如MongoDB,是直接在原数据上进行更新操作的,但也有许多NoSQL系统是采用了上面说到的日志策略。
Cassandra和HBase借鉴了BigTable的做法,在数据结构上实现了一个日志型的查找树。
Riak也使用了类似的方法实现了一个日志型的hash表(译者:
也就是Riak的BitCask模型)。
CouchDB对传统的B+树结构进行了修改,使得对树的更新可以使用顺序的追加写操作来实现(译者:
这种B+树被称作append-onlyB-Tree)。
这些方法的使用,使得存储系统的写操作承载力更高,但同时为了防止数据异构后膨胀得过大,需要定时进行一些合并操作。
3.1.3.通过合并写操作提高吞吐性能
Cassandra有一个机制,它会把一小段时间内的几个并发的写操作放在一起进行一次fsync调用。
这种做法叫groupcommit,它导致的一个结果就是更新操作的返回时间可能会变长,因为一个更新操作需要等就近的几个更新操作一起进行提交。
这样做的好处是能够提高写操作承载力。
作为HBase底层数据支持的Hadoop分布式文件系统HDFS,它最近的一些补丁也在实现一些顺序写和groupcommit的机制。
3.2.多机可靠性
由于硬件层面有时候会造成无法恢复的损坏,单机可靠性的保证在这方面就鞭长莫及了。
对于一些重要数据,跨机器做备份保存是必备的安全措施。
一些NoSQL系统提供了多机可靠性的支持。
Redis采用了传统的主从数据同步的方式。
所有在master上执行的操作,都会通过类似于操作日志的结构顺序地传递给slave上再执行一遍。
如果master发生宕机等事故,slave可以继续执行完master传来的操作日志并且成为新的master。
可能这中间会导致一些数据丢失,因为master同步操作到slave是非阻塞的,master并不知道操作是否已经同步线slave了。
CouchDB实现了一个类似的指向性的同步功能,它使得一个写操作可以同步到其它节点上。
MongoDB提供了一个叫ReplicaSets的架构方制,这个架构策略使得每一个文档都会保存在组成ReplicaSets的所有机器上。
MongoDB提供了一些选项,让开发者可以确定一个写操作是否已经同步到了所有节点上,也可以在节点数据并不是最新的情况下执行一些操作。
很多其它的分布式NoSQL存储都提供了类似的多机可靠性支持。
由于HBase的底层存储是HDFS,它也就自然的获得了HDFS提供的多机可靠性保证。
HDFS的多机可靠性保证是通过把每个写操作都同步到两个以上的节点来实现的。
Riak、Cassandra和Voldemort提供了一些更灵活的可配置策略。
这三个系统提供一个可配置的参数N,代表每一个数据会被备份的份数,然后还可以配置一个参数W,代表每个写操作需要同步到多少能机器上才返回成功。
当然W是小