概念_Nosql精粹

第一章 为什么使用NoSQL

  • 关系型数据库存在阻抗失衡的问题(关系模型与内存中的数据结构不匹配)
  • 数据库领域的迁移趋势是:原来各个应用程序douban同一份数据库当做共用的集成点;而现在各个程序封装自己的数据库,通过服务相互继承
  • 使用NoSQL的原因是:
    • 待处理数据量很大或者对效率要求很高,从而必须要放在集群上执行
    • 想采用一种更方便的数据交互方式来提高数据访问效率
  • NoSQL的共同特征是
    • 不适用关系模型
    • 在集群中运行良好
    • 开源
    • 无模式
  • NoSQL崛起产生的影响就是混合持久化。就是不一味使用数据库,而是根据不同场景选用不同的数据存储技术

第二章 聚合数据库模型

NoSQL模型可以分为键值,文档,列族,图四种,前三种有一个共同的特征就是”面向聚合”。关系数据库将信息分割成元组,元组间不能嵌套。而聚合指的是把一组互相关联的对象视为一个整体单元来操作,这个单元就是聚合。

对于聚合数据库来说,不需要严格按照关系数据库进行拆分,而应该按照业务需求来拆数据,一些时候甚至能将整个数据存在一条数据中。典型的就是json这种嵌套格式。

使用“聚合无知模型”可以很方便的以不同方式来看数据,因此在操作数据时,如果没有一种占主导地位的结构,那么选用此模型比较好。而选用面向聚合模型的决定性因素在于他非常适合在集群上运行。如果数据库中明确包含了聚合结构,那么他就可以根据这一重要信息知道这些数据就会被一起操作,并放在同一节点中。

对于ACID事务,(atomic原子性,consistent一致性,isolated隔离性,durable持久性)关系型数据库支持表之间的ACID,而面向聚合的NoSQL一般不支持跨越多个聚合的ACID,所以需要业务代码来保证。

  • 键值数据库:聚合不透明,对数据的限制较小。访问聚合内容只能通过键来查找,并且将整个聚合作为一个整体来操作
  • 文档数据库:定义了语序的结构与数据类型,能更加灵活地访问数据。但是文档没有模式,想优化存储并获取聚合的具体内容时,数据库不好调整文档结构(MongoDB)
  • 列族数据库:最经典的如hbase,采用两层结构,每行都有不同列族,列族下面有很多列。这里列族可以看做行聚合内部的一个单元,存储时可以按照列族进行划分。

第三章 数据模型详解

图数据库

基于聚合的NoSQL很难处理大量关系,而关系型数据库也不尽如人意。图数据库就是为了处理这种复杂关系的场景而诞生的。

  • 图数据的基本模型:由边连接成的若干节点。将图结构搭好后,可以使用专门为图而设计的查询操作来搜寻图数据库的网络。为了提高查询效率,图数据库会多耗一些时间用于插入关系数据。
  • 图数据库和面向聚合的数据库的明显差别,在于图数据库重视数据间的“关系”,这种数据模型上的差异也导致了其他一些区别。图数据库一般运行在单一服务器上而非集群中。为了使数据保持一致,ACID事务需要涵盖多个节点与边。相似之处是,它们都不适用关系模型。

无模式数据库

  • 各种形式的NoSQL都有一个特性,就是它们都是无模式的。在关系型数据库中,我们首先要定义好模式,就是用一种预定义结构向数据库说明,有哪些表,表中有哪些列,每列存放什么类型的数据,必须先定义好模式,才能使用数据库。相反,无模式数据库就显得相当随意了,可以在一个键下存放任意数据,提升了存储的灵活性。
  • 无模式数据库没有关系型数据库的那么多限制,但是它本身也存在一些问题,不管数据库无模式到什么程度,它都存在“隐含模式”,它是指在编写数据操作代码时,对数据结构所做的一系列假设。虽然我们可以在无模式数据库中以合法名称的键值存放数据,但是这也就假定了程序需要知道这些字段。
  • 应用程序代码中的隐含模式也会带来一些问题,它意味
    1. 着要想理解数据库中的数据,必须深入研究应用程序的代码才行。
    2. 由于数据库感知不到模式,也就无法自行验证数据,只能通过应用程序进行验证。

从本质上说,无模式数据库是把模式交由访问其数据的应用程序代码来处理,如果有多个应用程序需要访问同一个数据库,可能会出现问题。一种缓解此问题的方法是把数据互动操作封装成独立的应用程序,并通过web服务的形式将它和其他应用程序集成。另一种方法是在聚合中明确划分出不同应用程序的区域。

物化视图

  • 关系型数据库可以利用视图来展示一些需要复杂计算才能给出的数据,视图就好比一张关系表,只不过它是通过基表计算得来的,在访问视图时,数据库会计算出视图中的数据,这种形式的封装很方便。有了视图这种机制,客户端不需要担心它访问到的是基本数据还是派生数据,不过生成视图需要大量计算,比较消耗性能。
  • 物化视图是一种事先计算好并缓存起来的视图,如果数据读取频繁,且对实时性要求不高的话,可以使用物化视图来优化性能。在NoSQL中,可以利用物化视图来处理这种需求。面向聚合的数据库更强调这个问题,因为大多数应用程序都要处理某种与聚合机构不相符的查询操作。
  • 构建物化视图有两种方法,第一种是在每次基础数据变更的时候都去更新物化视图,如果读取物化视图的次数远比写入的多,且想获得更为实时的数据,那么这种方法比较合适。第二种是通过批处理来定时更新物化视图,这需要根据业务需求来制定方案,需要考察业务能容忍过时多久的数据。
  • 面向聚合的数据库通常能够用不同方式重组主聚合的数据,以计算出各种物化视图,计算过程一般使用map-reduce的方式。

构建数据存储模型

使用列族数据库建模时,应该按照查询需求而不是写入需求来处理。建模的通则是要便于查询,而且在写入数据时要对其执行“反规范化”操作。(规范化要求我们尽可能打散信息,以便能降低数据的冗余度,提高数据操控的精确度)

第4章 分布式模型

  • 面向聚合数据库非常适合于横向扩展方式,因为聚合此时就自然成了数据分布单元。
  • 数据分布有两条途径:
    1. 复制
    2. 分片
  • 复制,就是将同一份数据拷贝至多个节点。分片,就是将不同数据存放在不同节点中。复制和分片是两项正交的技术,它们既可以选其一使用,也可以结合使用。

分片

  • 分片是把数据的各个部分放在不同的服务器(节点)中,以此实现横向扩展。理想情况是保证需要同时访问的数据在同一个节点上。
    • 面向聚合的数据中,聚合结构就是将经常需要访问的数据放在一起,所以可以很方便的将聚合作为分布数据的单元
    • 地理上可以将数据放在离用户近的数据中心
  • 分片同时要尽量保证负载均衡。很多NoSQL都提供了自动分配的技术。
  • 复制技术可以提升读效率,但是对于频繁写的场景优化不大,分片提供了可以横向扩展写入能力的方式。
  • 从单一节点向分片迁移在生产环境很难,因此应当提前准备,在有余力时尽快迁移数据

主从复制

将数据复制在多个节点间复制。其中一个节点叫主节点,主节点用于存放权威数据,并且通常负责处理数据更新操作。其余节点都叫”从节点“,复制操作就是让从节点和主节点同步。主从复制的好处有两点:

  • 有助于提升数据读性能
  • 增强读取造作的故障恢复能力,万一主节点出错了,从节点依然可以处理读取请求。
  • 主从复制中主节点依然是瓶颈。同时主节点向从节点复制时会有一定时间的不一致

对等复制

  • 通知给其他所有节点。
  • 增加节点可以轻易提升性能。
  • 对等复制也存在数据一致性问题,由于两个不同节点可以同时处理写入操作,所以可能出现两位用户同时对同一条记录的情况,这就导致“写入冲突”。数据读取的不一致性也存在,但持续时间相对较短,因为最终都会归于一致,写入不一致却总是存在。
  • 写入不一致的几种解决方案思路:
    1. 不管何时写入数据,各副本之间总能相互协调,确保不发生冲突,这需要花费一定的网络流量来协调写入操作。
    2. 设法处理这些不一致的写入操作,比如合并这些操作。

结合”分片”与”复制”技术

  • 主从复制与分片结合:整个系统有多个主节点,但是对于每条数据,只有一个主节点
  • 对等复制与分片:一开始指定复制因子,一旦某个节点出错,数据由其他节点重建

第五章 一致性

关系型数据库试图通过“强一致性”来避免各种不一致问题,在NoSQL领域中,需要我们自己思考系统需要何种一致性。

更新一致性

并发环境下维护一致性通常分为悲观方式和乐观方式:

  • 悲观方式:避免冲突,比如加锁
  • 乐观方式:先让冲突发生,然后检测冲突并对发生冲突的操作排序
    • 条件更新:更新前测试当前值和上一次读取值是否一致
    • 另一种方法,常用在分布式版本控制中:将两份数据都存起来并标明,之后按照某种规则合并,比如手动合并或者定义规则合并

悲观模式很安全,但是效率较低。并发编程就是要在安全性和响应能力间做权衡。

采用复制模式时,除了对等复制,其他的一般采用所有写操作交由一个节点维护来保证更新一致性

读取一致性

  • 数据库必须具有更新一致性,但就这样还不够,它未必能保证用户所提交的访问请求总是能得到一致的响应。典型场景是一个用户A的一个更新动作需要顺序操作两张表的数据,如果是面向聚合的数据库则此处无法使用ACID事务,因此是依次更新。如果在更新完第一张表但还未更新第二张表时,用户B访问了第二张表的那条数据,就会看到一个不一致的数据。这种一致性叫“逻辑一致性”。
  • 并不是所有NoSQL数据库都不支持ACID事务,只有面向聚合的数据库不支持,图数据库是支持ACID事务的。
  • 面向聚合数据库可以“原子地”更新一个聚合的数据,但仅限于单一聚合内部。所以说,“逻辑一致性”可以在某个聚合内部保持,但各聚合之间则不行。在多个聚合之间更新数据就存在一个时间空档,在此空档内读出的相关数据不满足“逻辑一致性”,这个空档叫“不一致窗口”。NoSQL系统的“不一致窗口”一般很短暂。
  • 在引入复制的场景下,就会遭遇一种全新的不一致问题,叫“复制不一致”,即不同副本中读取同一个数据项结果不同。如果有多个节点,在个节点上更新了数据,在所有节点尚未全部同步数据前,就会有部分用户访问到过期的数据。但最终,更新还是会传播到所有节点上,这叫“最终一致性”。
  • “复制一致性”和“逻辑一致性”虽说是两个独立的问题,不过如果“复制”过程中的“不一致窗口”太长,就会加剧“逻辑不一致”问题。两个时间间隔很短且内容不同的更新操作,在主节点中留下的“不一致窗口”也就几毫秒而已,但由于网络延迟,这个“不一致窗口”在从节点上会比在主节点上长得多。
  • “会话一致性”是指在用户会话内部保持“照原样读出所写内容的一致性”。要确保“会话一致性”,其中一种方法是使用“粘性会话”,就是把会话绑定到某个固定的节点,但缺点是会降低负载均衡器的效率。另一种方法是使用“版本戳”,这个之后会详述。

放宽一致性约束

系统设计中,我们常常会放松对一致性的要求,比如事务系统中的放松隔离级别(最高隔离级别是可序列化,最常试用的隔离级别是只能读取已提交的数据),或者弃用事务来提升性能。

CAP定理

CAP指的是下面三个条件,我们只能满足两个

  • 一致性(consistency):所有client读到的数据都一样
  • 可用性(abailablity):在单服务器系统中讲的是每个client都能读写数据库。但是在集群中可用解释为系统中某个无故障节点所接受的每一条请求,无论成功失败,都能得到响应。
  • 分区容忍性(partition):如果发生通信故障,导致整个集群被分割陈多个无法互相通信的分区时,集群仍然可用

而他给我们带来的指导作用是,当系统可能会遭遇分区时,需要在一致性和可用性之间做权衡(更准确的说是在一致性和延迟之间做权衡)。在业务可以允许的范围内,可以略微舍弃一致性,提高可用性。比如购物车不要求实时一致,在不同节点都可以添加,只是付款前将数据合并并且交给用户做一遍判断是不是买多了。

cap

BASE属性::(基本可用,柔性状态,最终一致性。Basically Available, Soft stat, Eventual consistency)。

放宽“持久性”约束

  • 某些数据可以不持久化或延迟持久化,比如用户session或者一些临时数据可以保存在redis中,生成和更新非常频繁但又不是那么重要的数据可以延迟持久化,比如定时持久化写入。
  • 是否放宽“持久化”约束需要根据具体需求来确定。

仲裁

  • 为了保证强一致性,写入操作中,参加写入的节点数W超过所有副本节点数N的一半即可。
  • 读取操作所需要联系的节点数R与参与写入的节点数W相关,需要满足R+W>N
  • 一般情况下会设置N=3,W=3,R=1。这时写会比较慢,但是读会比较快。其他业务场景也可以进行权衡。

第六章 版本戳

NoSQL下常使用乐观离线锁,是一种条件更新,即comapre and swap。版本戳是一种良好的compare字段。版本戳有几种生成办法。

  • 使用计数器,每次资源更新则加一。
    • 优点:可以由他的值确定那个比较新
    • 缺点:需要有一个主节点保证不同版本的计数器不会重复
  • 创建GUID(globally nique identifier)。
    • 优点:不用担心重复
    • 缺点:无法直接比较版本新旧
  • 内容哈希码:
    • 优点:只要内容相同就一样
    • 缺点:无法直接比较版本新旧
  • 使用上一次的时间戳
    • 优点:可以确定版本先后
    • 缺点:1.对于更新频繁的数据需要注意精度。2.多节点之间时间必须同步

在分布式环境中,可以使用“数组式版本戳”,来检测不同节点之间是否发生了“相互冲突的更新操作”。比如一个数据有三份副本blue,green,black,则记录[blue:43,green:54,black:12]。blue节点更新时则给自己的节点版本加1:[blue:44,green:54,black:12]。这样数组加和最大的数就是最新版本。如果两个节点的版本戳不一致并且和相同,则说明出现了冲突(比如两个节点同时更新了自己)。

第七章 映射-化简

就是mapReduce的思想


本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证,欢迎转载,但转载请注明来自http://thousandhu.github.io,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://thousandhu.github.io/2016/10/13/概念-Nosql精粹/