@nullcc
2016-07-11T17:03:40.000000Z
字数 10478
阅读 1631
数据库
NoSQL
NoSQL数据库数据模型的一般分类:
常见NoSQL数据库:
数据库应用趋势:
由于数据量越来越大,大型系统的扩展方式由数据库在单一计算机上的纵向扩展->在计算机集群中的横向扩展
混合持久化(关系型数据库 + NoSQL数据库)
关系型数据库和应用程序之间的“阻抗不匹配”。关系模型和应用程序程序中内存数据结构的不一致导致这种状况,因此涌现出很多对象-关系映射(ORM)框架,但滥用ORM可能会导致性能问题。
“集成数据库”和“应用程序数据库”,前者是一种多应用程序分享使用同一数据库的方式,特点是所有应用程序都可以获得自己想要的东西,但由于要兼容所有应用程序,因此数据库的设计可能会过分复杂导致不好维护,并且也需要应对系型数据库和应用程序之间的“阻抗不匹配”。后者衍生除了web服务,应用程序之间以接口的方式进行交互,通常使用XML或JSON来交换数据,此时就可以使用结构丰富的数据格式(数组、嵌套结构等)了。
web服务经常使用传输文本信息的HTTP协议,对一些性能要求较高的情况,可以使用二进制协议。
关系型数据库和计算机集群的格格不入。关系型数据库能把数据划分为几个集合,分别部署在各自独立的服务器上运行,这可以有效地对数据库分片。但这么做也有缺点,应用程序必须控制所有分片,它要知道数据库中的每份小数据存放在哪里才行。而且,查询、参照完整性、事务、一致性控制等操作也无法再跨分片的环境下执行。
在集群中使用关系型数据库有可能遇到成本的问题(商用关系型数据库一般按服务器台数计费)。
出现了以谷歌和亚马逊为首的典型计算机集群用户,为此谷歌研发了BigTable,亚马逊则是Dynamo。
混合持久化需要把集成数据库迁移到应用程序数据库上,一般来说,应用程序数据库都可以采用NoSQL数据库,但集成数据库不适合使用NoSQL数据库。企业可以考虑把原来存放在集成数据库中的数据转移到各个应用程序数据库,然后使用web服务来连接这些系统。这既降低了应用之间的耦合度,也降低了成本和维护难度。
选用NoSQL数据库的两个主要原因:
NoSQL的特征:
聚合结构可以把关联数据直接嵌入在某个聚合中,从而方便查找。聚合使数据库在集群上管理数据存储更为方便。
对某些数据交互有用的聚合结构,可能会阻碍另一些数据交互。在对数据建模时,需要考虑到哪种场景占主导地位,据此来设计数据聚合的方式。
选用面向聚合模型的决定性因素,就是它很适合在集群上运行。我们需要把采集数据时所需的节点数将至最小。如果在数据库中明确包含聚合结构,那么数据库就知道这些数据就会被一起操作,并放在同一节点中。
通常情况下,面向聚合的数据库不支持跨越多个聚合的ACID事务。聚合是作为交互单元的数据集合,数据库中的ACID操作以聚合为界。
键值数据库、文档数据库、列族数据库都属于面向聚合的数据库。
如果数据交互大都在同一聚合内执行,则应使用面向聚合的数据库;如果数据交互操作需要使用多种不同格式的数据,那么最好选用“聚合无知式数据库”。
常见的图数据库有:FlockDB, Neo4J, Infinite Graph。
图数据库对于处理复杂关系的场景较为理想,例如社交网络、产品偏好、资格认定规则等包含复杂关系的数据。
对关系型数据库来说,操作内部相互关系比较紧密的数据模型通常需要使用很多JOIN语句,效率也较差。使用图数据库遍历关系,则非常迅速。主要原因是由于图数据库会多花一些时间用于插入关系数据,以此来缩短遍历关系时所需的时间。在一些查询效率高于插入效率的场合,这种权衡非常重要。
图数据库使用边和节点来存储数据,使用图数据库时,大部分时候都是在浏览各种关系。
图数据库和面向聚合的数据库的明显差别,在于图数据库重视数据间的“关系”,这种数据模型上的差异也导致了其他一些区别。图数据库一般运行在单一服务器上而非集群中。为了使数据保持一致,ACID事务需要涵盖多个节点与边。相似之处是,它们都不适用关系模型。
各种形式的NoSQL都有一个特性,就是它们都是无模式的。在关系型数据库中,我们首先要定义好模式,就是用一种预定义结构向数据库说明,有哪些表,表中有哪些列,每列存放什么类型的数据,必须先定义好模式,才能使用数据库。相反,无模式数据库就显得相当随意了,可以在一个键下存放任意数据。
无模式数据库没有关系型数据库的那么多限制,但是它本身也存在一些问题,不管数据库无模式到什么程度,它都存在“隐含模式”,它是指在编写数据操作代码时,对数据结构所做的一系列假设。虽然我们可以在无模式数据库中以合法名称的键值存放数据,但是这也就假定了程序需要知道这些字段,除非我们只是无脑地遍历数据结构依次打印键值对,然而这基本没有什么意义。
应用程序代码中的隐含模式也会带来一些问题,它意味着要想理解数据库中的数据,必须深入研究应用程序的代码才行,如果代码很清晰易懂,那么你可以根据它推断出数据的模式,然而这一点却无法保证,因为这完全取决于你的代码是否清晰。举个简单的例子,某个字段,在一些聚合中保存为字符串,在另一些聚合中保存为对象(因为数据库是无模式的,理论上完全可能出现这种情况),如果你不知晓代码的逻辑,基本无法判断出这个字段的这两种数据类型究竟有什么区别。而且,由于数据库感知不到模式,也就无法自行验证数据,这就少了一道保障。不同应用程序可能也会以完全不同的方式使用某个字段。当然了,在验证数据方面,我们还必须在所有使用这个数据库的应用程序中都加上完全相同的验证逻辑来确保数据正确性和安全性。
从本质上说,无模式数据库是把模式交由访问其数据的应用程序代码来处理,如果有多个应用程序需要访问同一个数据库,可能会出现问题。一种缓解此问题的方法是把数据互动操作封装成独立的应用程序,并通过web服务的形式将它和其他应用程序集成。另一种方法是在聚合中明确划分出不同应用程序的区域。
关系型数据库可以利用视图来展示一些需要复杂计算才能给出的数据,视图就好比一张关系表,只不过它是通过基表计算得来的,在访问视图时,数据库会计算出视图中的数据,这种形式的封装很方便。有了视图这种机制,客户端不需要担心它访问到的是基本数据还是派生数据,不过生成视图需要大量计算,比较消耗性能。
物化视图是一种事先计算好并缓存起来的视图,如果数据读取频繁,且对实时性要求不高的话,可以使用物化视图来优化性能。在NoSQL中,可以利用物化视图来处理这种需求。面向聚合的数据库更强调这个问题,因为大多数应用程序都要处理某种与聚合机构不相符的查询操作。
构建物化视图有两种方法,第一种是在每次基础数据变更的时候都去更新物化视图,如果读取物化视图的次数远比写入的多,且想获得更为实时的数据,那么这种方法比较合适。第二种是通过批处理来定时更新物化视图,这需要根据业务需求来制定方案,需要考察业务能容忍过时多久的数据。
面向聚合的数据库通常能够用不同方式重组主聚合的数据,以计算出各种物化视图,计算过程一般使用map-reduce的方式。
面向聚合数据库非常适合于横向扩展方式,因为聚合此时就自然成了数据分布单元。
数据分布有两条途径:
复制,就是将同一份数据拷贝至多个节点。分片,就是将不同数据存放在不同节点中。复制和分片是两项正交的技术,它们既可以选其一使用,也可以结合使用。
分片是把数据的各个部分放在不同的服务器(节点)中,以此实现横向扩展。分片技术能有效实现负载均衡。为了最大程度地利用分片技术,我们必须保证需要同时访问的数据都存放在同一个节点上,并且节点必须排布好这些数据块,使访问速度最优。这些措施包括,使服务器服务于地理位置上较近的用户、最大程度实现负载均衡,某些情况下可以把需要依次读取的聚合放在一起。
经常有人通过应用程序来处理分片,比如把用户姓氏首字母按照A-D,E-G之类的方式存放在不同分片中,但这样做就把编程模型复杂化了,因为应用程序需要把查询操作分布到多个分片上。如果想调整分片,就需要修改应用程序的相关逻辑并迁移数据。很多NoSQL数据库都提供了“自动分片”来让数据库自己负责把数据分布到各分片,并将查询请求路由至适当分片中。
单一使用分片技术在应对数据库故障时比较力不从心,一旦某个节点崩溃,则该分片上的数据就不可访问,虽说可能只是其中一个分片出问题,但比较已经无法对外提供完整的服务了。后面会看到采用分片结合复制的技术构造冗余可以缓解此问题。
在主从复制中,有一个节点叫节点,其余的叫从节点。主节点存放权威数据,复制操作就是要让从节点和主节点的数据保持同步。
主节点负责处理数据的读写,从节点只负责数据读。这样只要增加从节点就可以实现横向扩展。
可以采用手动指定主节点和自动选择主节点两种模式,手动指定需要我们自己配置集群,自动选择比较简单,会让它们自动选举出主节点,如果之后这个主节点崩溃了,会自动指派新的主节点,减少故障时间,手动指定则无此福利。
主从复制有助于增强读取操作的故障恢复能力。
主从复制对于写入操作非常频繁的场合,虽然能将读取操作分流,稍稍缓解主节点写入数据的压力,但对写入操作的性能并没有什么显著的提升。
复制技术再带来好处的同时也有一个致命弱点,就是数据的不一致。如果主节点更新了一个数据,但还未通知从节点,那么用户从从节点读出的数据就不是最新的,甚至由于各从节点同步速度的快慢数据还是各异的。更极端的情况是,主节点崩溃了,此时有部分数据尚未同步到从节点,这部分数据就会丢失。
主节点仍然是系统的瓶颈和弱点。
对等复制中的所有节点都是平等的,不存在主从节点一说,所有节点都可以接受读写请求,节点会将自身的写入操作通知给其他所有节点。
增加节点可以轻易提升性能。
对等复制也存在数据一致性问题,由于两个不同节点可以同时处理写入操作,所以可能出现两位用户同时对同一条记录的情况,这就导致“写入冲突”。数据读取的不一致性也存在,但持续时间相对较短,因为最终都会归于一致,写入不一致却总是存在。
写入不一致的几种解决方案思路:
多个请求同时更新同一条数据会产生写冲突问题,在并发环境下维护数据一致性一般有两种方式:悲观方式和乐观方式。
悲观和乐观的方式,都有一个先决条件,就是更新顺序必须一致,这在单机环境下显然成立,但在分布式集群下,可能两个节点会以不同的顺序执行操作,最终数据就会不一致。
在分布式系统的并发问题上,需要有“顺序一致性”,就是所有节点都要保证以相同的次序执行操作。
数据库必须具有更新一致性,但就这样还不够,它未必能保证用户所提交的访问请求总是能得到一致的响应。典型场景是一个用户A的一个更新动作需要顺序操作两张表的数据,如果是面向聚合的数据库则此处无法使用ACID事务,因此是依次更新。如果在更新完第一张表但还未更新第二张表时,用户B访问了第二张表的那条数据,就会看到一个不一致的数据。这种一致性叫“逻辑一致性”。
并不是所有NoSQL数据库都不支持ACID事务,只有面向聚合的数据库不支持,图数据库是支持ACID事务的。
面向聚合数据库可以“原子地”更新一个聚合的数据,但仅限于单一聚合内部。所以说,“逻辑一致性”可以在某个聚合内部保持,但各聚合之间则不行。在多个聚合之间更新数据就存在一个时间空档,在此空档内读出的相关数据不满足“逻辑一致性”,这个空档叫“不一致窗口”。NoSQL系统的“不一致窗口”一般很短暂。
在引入复制的场景下,就会遭遇一种全新的不一致问题,叫“复制不一致”。如果有多个节点,在个节点上更新了数据,在所有节点尚未全部同步数据前,就会有部分用户访问到过期的数据。但最终,更新还是会传播到所有节点上,这叫“最终一致性”。
“复制一致性”和“逻辑一致性”虽说是两个独立的问题,不过如果“复制”过程中的“不一致窗口”太长,就会加剧“逻辑不一致”问题。两个时间间隔很短且内容不同的更新操作,在主节点中留下的“不一致窗口”也就几毫秒而已,但由于网络延迟,这个“不一致窗口”在从节点上会比在主节点上长得多。
“会话一致性”是指在用户会话内部保持“照原样读出所写内容的一致性”。要确保“会话一致性”,其中一种方法是使用“粘性会话”,就是把会话绑定到某个固定的节点,但缺点是会降低负载均衡器的效率。另一种方法是使用“版本戳”,这个之后会详述。
一致性很重要,不过有时必须舍弃它。在设计系统时,我们经常需要牺牲一致性来换取其他特性。
关系型数据库一般用事务来加强一致性,但是事务会影响系统性能。
CAP定理,其中CAP的意思是
CAP定理所表述的是,给定一致性、可用性和分区耐受性这三个属性,我们只能同时满足其中两个属性。
某些数据可以不持久化或延迟持久化,比如用户session或者一些临时数据可以保存在redis中,生成和更新非常频繁但又不是那么重要的数据可以延迟持久化,比如定时持久化写入。
是否放宽“持久化”约束需要根据具体需求来确定。
假设某份数据需要复制到3个节点中,为了保证"强一致性",不需要所有节点都确认写入操作,只需其中两个节点(超过半数)确认即可。就是说如果发生两个发生冲突的写入操作,那么只有其中一个操作能为超过半数的节点所认可(W>N/2)。这就叫写入仲裁。
读取仲裁,是指想要读取保证能够读到最新的数据,必须联系多少个节点才行。
在采用“复制”技术的分布式模型中执行数据操作时,无需联系所有副本,只要为足够多的副本所认可,就能保持“强一致性”了。
执行读取操作时所需联系的节点数(R)、确认写入操作时需要联系的节点数(W)和复制因子(N)之间可以用一个不等式来表达:R+W>N。
版本戳用户标识数据的版本,典型情况是使用计数器版本戳,如果当前数据库中某条数据的版本戳是3,而用户请求更新的数据版本戳是2,说明在用户上一次读取到更新之间,该条数据发生了一次更新,可能是有其他人在此时更新了数据,所以这个用户的更新会失败。
版本戳一般可以使用计数器、GUID、内容哈希值、上一次更新的时间戳来表示,这几种方案各有优劣。也可以结合起来使用,比如CouchDB的版本戳就结合使用了内容哈希和计数器。
除了可以避免“更新冲突”之外,版本戳也有助于维护“会话一致性”。
在分布式环境中,可以使用“由版本戳构成的数组”,来检测不同节点之间是否发生了“相互冲突的更新操作”。
映射-化简是一种在集群上执行并发计算所用的模式。
映射操作从聚合中读出数据,将之缩减为相关键值对。映射操作每次只能读取一条记录,所以可在存放记录的节点上并发执行。
映射任务会生成很多具备同一关键字的值,而化简任务则将它们化简为单一的输出值。每个化简函数只操作与单个键相关的映射结果,所以多个化简函数可以根据关键字执行并发化简。
输入数据与输出数据形式相同的多个“化简函数”可归并为“管道”,以提高并发执行能力,并减少所需传输的数据量。
若某个“化简输出”的结果是下一个“映射操作”的输入,那么可以用“管道”组合映射-化简操作。
如果需要广泛使用映射-化简的计算结果,那么可以将其存储为“物化视图”。
可使用增量式映射-化简操作来更新“物化视图”,这样做只需要计算视图中发生改变的那部分数据即可,无需把全部数据从头算一遍。
Oracle | Raik |
---|---|
数据库实例 | Raik集群 |
表 | 存储区 |
行 | 键值对 |
rowid | 键 |
从API的角度来看,键值数据库是最简单的NoSQL数据库。客户端可以根据键查询值,设置键所对应的值。
Redis等键值数据库中,所存储的数据不一定非要是领域对象,任何数据结构都行。Redis可以存储list、set、hash等数据结构,还可以求差集、并集、交集和获取某个范围内的数值。
一致性,只有针对单个键的操作才具备一致性,一般就是set、get或del。
事务,不同键值数据库其事务规范不同,一般无法保证写入一致性。比如Raik采用仲裁。
查询功能,所有键值数据库都可以按照关键字来查询,但无法根据列值来查询。
数据结构,键值数据库一般不关心值,值可以是二进制、文本、JSON等。
可扩展性,很多键值数据库都可以采用分片技术。采用这种技术后,键的名字决定了负责存储该键的节点。像Raik这样的数据库,可以控制“CAP”定理中的参数,N(存放键值对的副本节点数)、R(顺利完成读取操作所需的最小节点数)和W(顺利完成写入操作所需的最小节点数)。
存放会话信息
用户配置信息
购物车数据
数据间关系
含有多项操作的事务
查询数据
操作关键字集合
Oracle | MongoDB |
---|---|
数据库实例 | MongoDB实例 |
模式 | 数据库 |
表 | 集合 |
行 | 文档 |
rowid | _id |
join | DBRef |
一致性,为了在MongoDB数据库中确保一致性,可以配置副本集,也可以规定写入操作必须等待所写数据复制到全部或是给定数量的从节点后,才能返回。提升一致性会降低写入效率。可以配置以增加副本集的读取效率。
事务,只支持单文档级别的事务,即原子事务。
可用性,文档数据库试图用主从式数据复制技术来增强可用性。多个节点保有同一份数据,即使主节点故障,客户端也依然可以获取数据,应用程序一般不需要检测主节点是否可用。所有请求都由主节点处理,而其数据会复制到从节点。如果主节点故障,副本集中剩余的节点会在其自身范围内选举出新的主节点。副本集通常用于处理数据冗余、自动故障切换、灾难恢复等事项。
查询功能,文档数据库可以查询文档中的数据,而不用像键值数据库那样,必须根据关键字获取整个文档,然后再检视其内容。MongoDB还可以基于“内嵌子文档”来查询。
可扩展性,增加更多的“读取从节点”,将全部读取操作都引导至从节点上,这样可以扩展数据库应对频繁读取的能力了。如果想扩展写入能力,则可以把数据分片,分片操作根据特定字段来划分数据(该字段的选择很重要),这些数据要移动到不同的节点中。为了让各分片负载保持均衡,需要在节点之间动态转移数据,向集群中加入更多节点,并提高可写入的节点数,就可以横向扩展写入能力。把每个分片都做成副本集可以提高读取效率。
事件记录
内容管理系统和博客平台
网站分析和实时分析
电子商务应用程序
包含多项操作的复杂事务
查询持续变化的聚合结构
关系型数据库 | Cassandra |
---|---|
数据库实例 | 集群 |
数据库 | 键空间 |
表 | 列族 |
行 | 行 |
列 | 列 |
一致性,Cassandra收到写入请求后,会先将待写入数据记录到“提交日志”中,然后将其写入内存中一个名为“内存表”的结构中。写入操作在写入“提交日志”和“内存表”后就算成功了。写入请求成批堆积在内存中,并定期写入一种叫做“SSTable”的结构中,该结构中的缓存一旦写入数据库,就不会再向其继续写入了。若其数据变动,则需新写一张SSTable。无用的SSTable可由“压缩”操作回收。
事务,Cassandra没有传统意义上的事务,它的写入操作在行级别的原子的。
可用性,Cassandra具备高可用性,因为集群中没有主节点,减少操作请求的一致性即可提示集群的可用性。可用性受制于 R+W>N 这一公式。W是成功执行写入操作所需的最小节点数,R是顺利执行读取操作所需获取的最小应答节点数,N是参与数据复制的节点数。
查询功能,Cassandra没有功能丰富的查询语言。在列族插入数据后,每行中的数据都会按照列名排序。如果一列的获取次数比其他列更加频繁,可以考虑将其值用作行健以提高性能。
基本查询,有GET、SET和DEL。
高级查询和索引编订,Cassandra列族可以用关键字以外的其他列当索引。这些索引以位映射图的形式出现,在列中频繁出现重复值的情况下效果较好。
Cassandra查询语言(CQL),提供查询功能,但未包含SQL的全部功能。
可扩展性,由于没有主节点,所以只要向Cassandra集群中新增节点就能改善其服务能力。
事件记录
内容管理系统和博客平台
计数器
限期使用
需要以“ACID事务”执行写入和读取操作的系统。
根据查询结构聚合数据的场景。
开发早期、原型期和技术初探期。开发初期无法确定查询模式的变化情况,查询模式一旦变化,列族的设计也要随之修改。注意,关系型数据库改变数据模式的成本很高,但查询模式的修改成本较低,Cassandra则相反。
一致性,由于图数据库操作互相连接的节点,所以大部分图数据库通常不支持把节点分布在不同服务器上。图数据库通过事务来保证一致性,它们不允许出现“悬挂关系”:所有关系必须具备起始节点和终止节点,而且在删除节点前,必须先移除其上的关系。
事务,Neo4J是兼容ACID事务的数据库,修改节点或向现有节点新增关系前,必须先启动事务。
可用性,Neo4J从1.8开始,支持“副本从节点”,这些从节点可以处理写入操作,向其写入后,它会先将所写数据同步至当前主节点,然后主节点再同步至其他从节点。也可以配合ZooKeeper来记录每个丛节点和当前主节点中最新的事务ID。
查询功能,图数据库可以使用Gremlin等查询语言,Neo4J还可以使用Cypher语言来查询图。
可扩展性,图数据库要运用分片技术比较难,因为它并不是面向聚合的,而是面向关系的。由于任何节点都有可能关联其他节点,因此把相关节点放在同一台机器上,遍历图时比较方便,放在多台机器上性能不好。扩展图数据库一般有三种方式:
互联数据
安排运输路线、分派货物和基于位置的服务
推荐引擎
更新全部或某个子集内实体的场合
涉及整张图的操作
若要迁移关系型数据库等“强模式”的数据库,可将历次模式变更及其数据迁移操作保存于“版本控制序列”中。
因程序代码要依照“隐含模式”来访问无模式数据库的数据,故其数据迁移仍需谨慎处理。
无模式数据库亦可借用强模式数据库的迁移技术。
无模式数据库可使用“增量迁移”技术更新数据,以便在不影响应用程序读取数据的前提下,修改数据的隐含模式。
混合持久化旨在使用不同数据库技术处理多种数据存储需求。
混合持久化既可为企业中多个程序所用,也可以运用在单个应用程序中。
将数据访问封装成服务,可以减少数据库变动对系统其它部分的影响。
新增数据库技术会让编程和操作更复杂,所以要权衡引入新数据库带来的好处和引入它带来的复杂度的利弊。
文件系统
事件溯源
内存映像
版本控制
XML数据库
对象数据量
通过使用更符合应用程序需求的数据库来改善程序员的工作效率。
以能处理大数据量、降低延迟且增进数据吞吐量的某种技术组合来改善数据访问性能。
在决定使用某个NoSQL技术之前,一定要测试其是否如预期般改善了程序员工作效率和数据访问性能。
用服务来封装数据库,即能在需求变更或技术成熟后改换其所封装的数据库技术。可将应用程序各部分划分到不同服务中,以便为既有程序引入NoSQL数据库。
大部分应用程序,尤其是“非战略性的”应用程序,应该继续使用关系型数据库技术,至少在NoSQL技术环节尚未更加成熟时是如此。