ch62 关系型数据库的价值

获取持久化数据

持久存储大量数据
在大多数的计算架构中,有两个存储区域:

  • 速度快但是数据易丢失的“主存储器”(main memory)
    • 空间有限
    • 易挥发
  • 存储量大但速度较慢的“后备存储器”(backing store)
    • 文件系统,如许多生产力应用程序(productivity application,比如文字处理软件)
    • 数据库,大多数企业级应用程序

      挥发性和非挥发性存储器跟掉电丢失与否有关。前者为掉电数据丢失。
      RAM为随机存取存储器,理论上断电后数据全丢失,但是非挥发性RAM内置了一个电源,有个检测系统是否掉电的电路,当监测到掉电时,即接通内部电源以确保时间保持和内存数据不受破坏。这相当于没有掉电,即RAM的数据也没丢失。

并发

多个用户会一起访问同一份数据体,并且可能要修改这份数据。(大多数情况下,他们都在不同数据区域内各自操作,但是,偶尔也会同时操作一小块数据)
关系型数据库提供了“事务”机制来控制对其数据的访问,以便处理此问题。
事务在处理错误时也有用。通过事务更改数据时,如果在处理变更的过程中出错了,那么就可以回滚(roll back)这一事务,以保证数据不受破坏。

集成

  • 企业级应用程序居于一个丰富的生态系统中,它需要与其他应用程序协同工作。不同的应用程序经常要使用同一份数据,而且某个应用程序更新完数据之后,必须让其他应用程序知道这份数据已经改变了。
  • 常用的办法是使用共享数据库集成(shared database integration) ,多个应用程序都将数据保存在同一个数据库中。这样一来,所有应用程序很容易就能使用彼此的数据了。
  • 与多用户访问单一应用程序时一样,数据库的并发控制机制也可以应对多个应用程序。

近乎标准的模型

关系型数据库以近乎标准的方式提供了数据模型。
尽管各种关系型数据库之间仍有差异,但其核心机制相同

  • 不同厂商的SQL方言相似
  • “事务” 的操作方式也几乎一样

ch63 NoSQL的由来

阻抗失谐

基于关系代数(relational algebra),关系模型把数据组织成 “关系”(relation)和“元组”(tuple)。

  • 元组是由“键值对”(name-value pair)构成的集合
  • 而关系则是元组的集合。
  • SQL操作所使用及返回的数据都是“关系”
  • 元组不能包含“嵌套记录”(nested record)或“列表”(list) 等任何结构

而内存中的数据结构则无此限制,它可以使用的数据组织形式比“关系”更丰富。
关系模型和内存中的数据结构之间存在差异。这种现象通常称为“阻抗失谐”。
如果在内存中使用了较为丰富的数据结构,那么要把它保存到磁盘之前,必须先将其转换成“关系形式”。于是就发生了“阻抗失谐”:需要在两种不同的表示形式之间转译
image.png

解决方法

  • 面向对象数据库
  • “对象-关系映射框架”( object-relational mapping framework) 通过映射模式( mapping pattern)表达转换
  • 问题:
    • 查询性能问题
    • 集成问题

集成数据库

SQL充当了应用程序之间的一种集成机制。数据库在这种情况下成了“集成数据库”(integration database)

  • 通常由不同团队所开发的多个应用程序,将其数据存储在一个公用的数据库中
  • 所有应用程序都在操作内容一致的持久数据,提高了数据通信的效率
  • 为了能将很多应用程序集成起来,数据库的结构比单个应用程序所要用到的结构复杂得多
  • 如果某个应用程序想要修改存储的数据,那么它就得和所有使用此数据库的其他应用程序相协调
  • 各种应用程序的结构和性能要求不尽相同,数据库通常不能任由应用程序更新其数据。为了保持数据库的完整性,我们需要将这一责任交由数据库自身负责。

应用程序数据库

将数据库视为“应用程序数据库”(application database),** 其内容只能由一个应用程序的代码库直接访问**
由于只有开发应用程序的团队才需要知道其结构,模式的维护与更新就更容易了。
由于应用程序开发团队同时管理数据库和应用程序代码,因此可以把维护数据库完整性的工作放在应用程序代码中。
交互工作转交由应用程序接口来完成

  • “面向服务架构” 、Web服务。使得应用程序间通过平台中立的方式完成集成
  • 在Web服务作为集成机制后,所交换的数据可以拥有更为灵活的结构
    • 如XML、 JSON格式,它们均能够使用嵌套记录及列表等更丰富的数据结构使用
    • “面向文档”的交互方式,减少通讯次数和开销
    • 既可以传输文本,也可以传输二进制

在使用应用程序数据库后,由于内部数据库与外部通信服务之间已经解耦,所以外界并不关心数据如何存储,这样就可以选用非关系型数据库了
关系型数据库的许多特性,诸如安全性等,可以**交给使用该数据库的外围应用程序(enclosing application)**来做

集群问题

纵向扩展(scale up)及横向扩展(scale out)

  • 采用集群应对横向扩展

    必须有更多的计算资源,才能应对数据和流量的增加。处理此类增长有两种方案:纵向扩展(scale up)及横向扩展(scale out)。如果要纵向扩展,那么就需要功能更强大的计算机,要购买更多的处理器、磁盘存储空间和内存。但是机器的功能越强,其成本也越高,更何况其扩展尺度也有限。另一种方案是:采用由多个小型计算机组成的集群。集群中的小型机可以使用性价比较高的硬件,这样就能降低扩展所需的成本。而且,这么做也更有弹性:我们可以构建一个高度稳定的集群,就算其中的某些电脑经常发生故障,也不会影响整个集群的运行。

关系型数据库的“分片”和“复制”

  • 在负载分散的同时,应用程序必须控制所有分片,需要知道数据库中的每份小数据的存储情况

  • 如何确保跨分片的查询、参照完整性(referential integrity)、 事务、一致性控制(consistency control)等操作

  • 纵向扩展(scale up)是指通过在节点上增加更多的CPU、内存和硬盘来扩大系统的能力。 这种方式适合于系统设计初期或者对数据实时性要求很高的场景,但是成本较高,且有单点故障的风险。

  • 横向扩展(scale out)是指通过增加节点的数量来提高系统的处理能力。这种方式适合于系统并发超过单机极限或者需要分布式计算的场景,但是也会引入一些复杂问题,如负载均衡、数据一致性、容错等。

  • 关系型数据库的“分片”(sharding)是指将一个大的数据库分割成多个小的数据库,每个小数据库只存储一部分数据,从而提高查询效率和可扩展性。

  • 关系型数据库的“复制”(replication)是指将一个数据库的数据完全或部分地复制到另一个数据库中,从而提高数据可用性和容错性

  • 跨分片的查询是指需要从多个分片中获取数据的查询,这种查询通常比单分片的查询更复杂和低效,因为需要合并不同分片的结果。

NoSQL

NoSQL没有规范的定义“开源分布式的非关系型数据库”
各种NoSQL数据库的共同特性是

  • 不使用关系模型:NoSQL数据库不将数据存储在固定的表格中,而是采用各种数据模型,如文档、键值、图形等,来适应不同的数据类型和结构
  • 在集群中运行良好
    • 关系型数据库使用ACID事务来保持整个数据库的一致性,而这种方式本身与集群环境相冲突
    • NoSQL数据库为处理并发及分布问题提供了众多选项。
  • 开源
  • 适用于21世纪的互联网公司
  • 无模式
    • 不用事先修改结构定义,即可自由添加字段了
    • 这在处理不规则数据和自定义字段时非常有用

      举例来说,如果我们使用一个文档型数据库,如MongoDB,来存储用户信息,我们不需要预先定义用户表的字段和类型,我们可以根据需要随时添加或修改用户文档的属性。例如,我们可以给某些用户添加一个“爱好”字段,而不影响其他用户文档。这样做的好处是可以灵活地适应用户信息的变化,而不需要频繁地修改数据库架构。但是这样做的坏处是可能导致用户信息的不一致性和冗余性,比如有些用户可能有多个“爱好”字段,或者有些用户可能没有“姓名”字段等。这就给数据管理和维护带来了挑战,比如如何保证数据的完整性和有效性,如何进行数据清洗和分析等。

为什么使用NoSQL

image.png

ch64 聚合

概念

把一组相互关联的对象视为一个整体单元来操作,而这个单元就叫聚合(aggregate)。

  • 通过原子操作(atomic operation)更新聚合的值(含一致性管理)
  • 以聚合为单位,进行数据存储通信
  • 在集群中操作数据库时,用聚合为单位来复制和分片
  • 聚合描述数据访问方式

面向聚合操作数据时所用的单元,其结构比元组集合复杂得多

  • “键值数据库”、“文档数据库”、“列族数据库”

关系模型和聚合模型

关系实例

image.png

image.png

聚合实例1

image.png

image.png

聚合实例2

image.png

image.png

聚合无知

关系型数据库的数据模型中,没有“聚合”这一概念,因此我们称之为“聚合无知”(aggregate-ignorant)。
“图数据库”也是聚合无知的。
聚合反应数据操作的边界,很难在共享数据的多个场景中“正确” 划分,对某些数据交互有用的聚合结构,可能会阻碍另一些数据交互

  • 在客户下单并核查订单,以及零售商处理订单时,将订单视为一个聚合结构就比较合适。
  • 如零售商要分析过去几个月的产品销售情况,如果将订单做成一个聚合,就必须深挖数据库中的每一个聚合。

聚合无知模型是指不使用聚合的数据模型,而是将数据对象作为独立的实体来存储和操作。这样做的好处是可以根据不同的需求和场景来组织和查询数据对象,而不受固定的聚合结构的限制。
若是采用“聚合无知模型”,如果没有一种占主导地位的结构,那么很容易就能以不同方式(查询方式)来查看数据

聚合之间的关系

例如:把订单和客户放在两个聚合中,但是想在它们之间设定某种关系,以便能根据订单查出客户数据

  • 要提供这种关联,最简单的办法就是把客户ID嵌入订单的聚合数据中。
  • 在应用层级提供关联,在数据库层级提供聚合之间关系的表达机制

操作多个有关联的聚合,由应用保证其正确性
面向聚合数据库获取数据时以聚合为单元,只能保证单一聚合内部内容的原子性

举例来说,如果我们有一个订单聚合和一个库存聚合,我们想要实现一个下单的操作,那么我们需要在应用程序中做以下几步:

  • 查询订单聚合,检查订单的状态和金额等信息。
  • 查询库存聚合,检查库存是否足够。
  • 如果库存足够,更新订单聚合,将订单状态改为已支付。
  • 如果库存足够,更新库存聚合,将库存数量减少相应的数量。

在这个过程中,我们需要在应用程序中保证订单和库存之间的关联关系的正确性
比如如果订单更新失败了,我们需要回滚库存的更新。同时,我们只能保证对单个聚合的操作是原子性的,比如如果订单更新成功了,但是库存更新失败了,数据库无法自动撤销订单的更新(因为操作是原子性的,需要手动处理这些异常情况)。这就可能导致数据的不一致性和异常情况。因此,在操作多个有关联的聚合时,我们需要在应用程序中使用一些机制来保证事务性和一致性

聚合、集群和事务处理

  • 在集群上运行时,需要把采集数据时所需的节点数降至最小

如果在数据库中明确包含聚合结构,那么它就可以根据这一重要信息,知道哪些数据需要一起操作了,而且这些数据应该放在同一个节点中

  • 通常情况下,面向聚合的数据库不支持跨越多个聚合的ACID事务。它每次只能在一个聚合结构上执行原子操作。
    • 如果想以原子方式操作多个聚合,那么就必须自己组织应用程序的代码
    • 在实际应用中,大多数原子操作都可以局限于某个聚合结构内部,而且,在将数据划分为聚合时,这也是要考虑的因素之一

ch65 主要的NoSQL数据模型?

键值数据模型与文档数据模型

这两类数据库都包含大量聚合,每个聚合中都有一个获取数据所用的键或ID。
两种模型的区别是:

  • 键值数据库的聚合不透明,只包含语义中立的大块信息
    • 数据库可能会限制聚合的总大小,但除此之外,聚合中可以存储任意数据。
    • 在键值数据库中,要访问聚合内容,只能通过键来查找
  • 在文档数据库的聚合中,可以看到其结构
    • 限制其中存放的内容,它定义了其允许的结构与数据类型能够更加灵活地访问数据。
    • 通过用聚合中的字段查询,可以只获取一部分聚合,而不用获取全部内容
    • 可以按照聚合内容创建索引

列族存储数据模型

  • 大部分数据库都以行为单元存储数据。然而,有些情况下写入操作执行得很少,但是经常需要一次读取若干行中的很多列。此时,列存储数据库将所有行的某一组列作为基本数据存储单元
  • 列族数据库将列组织为列族。每一列都必须是某个列族的一部分,而且访问数据的单元也得是列某个列族中的数据经常需要一起访问

列族模型将其视为两级聚合结构(two-level aggregate structure)。
第一个键通常代表行标识符,与“键值存储”相同,可以用它来获取想要的聚合
列族结构与“键值存储”的区别在于,其“行聚合”(row aggregate)本身又是一个映射,其中包含一些更为详细的值。这些“二级值” (second-level value)就叫做“列”。与整体访问某行数据一样,我们也可以操作特定的列

image.png

两种数据组织方式

  • 面向行(row-oriented):每一行都是一个聚合(例如ID为1234的顾客就是一个聚合),该聚合内部存有一些包含有用数据块(客户信息、订单记录)的列族
  • 面向列(column-oriented): 每个列族都定义了一种记录类型(例如客户信息),其中每行都表示一条记录。数据库中的大“行”理解为列族中每一个短行记录的串接

面向聚合的数据模型

共同点

  • 都使用聚合这一概念,而且聚合中都有一个可以查找其内容的索引键
  • 在集群上运行时,聚合是中心环节,因为数据库必须保证将聚合内的数据存放在同一个节点上
  • 聚合是“更新”操作的最小数据单位(atomic unit),对事务控制来说,以聚合为操作单元

差别

  • 键值数据模型将聚合看作不透明的整体,只能根据键来查出整个聚合,而不能仅仅查询或获取其中的一部
  • 文档模型的聚合对数据库透明,于是就可以只查询并获取其中一部分数据了,不过,由于文档没有模式,因此在想优化存储并获取聚合中的部分内容时,数据库不太好调整文档结构
  • 列族模型把聚合分为列族,让数据库将其视为行聚合内的一个数据单元。此类聚合的结构有某种限制,但是数据库可利用此种结构的优点来提高其易访问性。

图数据库

image.png
图数据库的基本数据模型:由边(或称“弧”,arc)连接而成的若干节点。
可以用专门为“图”而设计的查询操作来搜寻图数据库的网络了:指定节点,通过边进行查询
关系型数据可以通过“外键”实现,查询中的多次连接,效率较差

无模式

关系型数据库中,首先必须定义“模式”,然后才能存放数据。
NoSQL数据库,无模式:

  • “键值数据库”可以把任何数据存放在一个“键”的名下。
  • “文档数据库” 对所存储的文档结构没有限制
  • 列族数据库中,任意列里面都可以随意存放数据
  • 图数据库中可以新增边,也可以随意向节点和边中添加属性。

格式不一致的数据

每条记录都拥有不同字段集(set of field)
关系型数据库中,“模式”会将表内每一行的数据类型强行统一,若不同行所存放的数据类型不同,那这么做就很别扭

  • 要么得分别用很多列来存放这些数据,而且把用不到的字段值填成null(这就成了“稀疏表”,sparse table)
  • 要么就要使用类似custom column 4这样没有意义的列类型。

无模式表则没有这么麻烦,每条记录只要包含其需要的数据即可,不用再担心上面的问题了

无模式的问题?

存在“隐含模式”。在编写数据操作代码时,对数据结构所做的一系列假设

  • 应用与数据的耦合问题
  • 无法在数据库层级优化和验证数据

在集成数据库中,很难解决

  • 使用应用程序数据库,并使用Web Services、SOA等完成集成
  • 在聚合中为不同应用程序明确划分出不同区域
    • 在文档数据库中,可以把文档分成不同的区段(section)
    • 在列族数据库,可以把不同的列族分给不同的应用程序

      根据应用程序的需求,只访问所需的区域,减少数据传输和处理的开销。

      根据区域的重要性和敏感性,设置不同的访问权限和安全策略,保护数据的完整性和隐私性。

      根据区域的变化频率和更新方式,设置不同的备份和恢复策略,提高数据的可用性和可靠性。

ch66 分布式模型

数据分布

数据分布有两条路径:分片(sharding)与复制(replication) 。

  • “分片”则是将不同数据存放在不同节点中
  • “复制”就是将同一份数据拷贝至多个节点

“主从式”(master-slave)和“对等式”(peer-to-peer)
既可以在两者中选一个来用,也可以同时使用它们。

单一服务器

最简单的分布形式:根本不分布。

  • 将数据库放在一个节点中,让它处理对数据存储的读取与写入操作
  • 不用考虑使用其他方案时所需应对的复杂事务,这对数据操作管理者与应用程序开发者来说,都比较简单。

尽管许多NoSQL数据库都是为集群运行环境而设计的,但是只要符合应用程序需求,那就完全可以按照单一服务器的分布模型来使用

  • 图数据库配置在一台服务器上
  • 如果只是为了处理聚合,那么可以考虑在单一服务器上部署“文档数据库”或“键值数据库”

分片

image.png

节点分布

在理想情况下,不同的服务器节点会服务于不同的用户每位用户只需与一台服务器通信,并且很快就能获得服务器的响应。网络负载相当均衡地分布于各台服务器上
为达成目标,必须保证需要同时访问的那些数据都存放在同一节点上,而且节点必须排布好这些数据块,使访问速度最优
若使用面向聚合的数据库,可以把聚合作为分布数据的单元。
在节点的数据排布问题上,有若干个与性能改善相关的因素。

  • 地理因素
  • 负载均衡
  • 聚合有序放置

实现方式

  1. 采用应用程序的逻辑实现分片
  • 编程模型复杂化,因为应用程序的代码必须负责把查询操作分布到多个分片上
  • 若想重新调整分片,那么既要修改程序代码,又要迁移数据
  1. 采用NoSQL数据库提供的“自动分片”(auto-sharding)功能
  • 让数据库自己负责把数据分布到各分片
  • 并且将数据访问请求引导至适当的分片

优缺点

  • 分片可以同时提升读取与写入效率
    • 使用“复制”技术,尤其是带缓存的复制,可以极大地改善读取性能,但对于写操作帮助不大
  • 分片对改善数据库的“故障恢复能力”帮助并不大。尽管数据分布在不同的节点上,但是和“单一服务器”方案一样,只要某节点出错,那么该分片上的数据就无法访问了
    • 在发生故障时,只有访问此数据的那些用户才会受影响,而其余用户则能正常访问
    • 由于多节点问题,从实际效果出发,分片技术可能会降低数据库的错误恢复能力

复制

主从复制

在“主从式分布”(master-slave distribution)中

  • 其中有一个节点叫做“主(master) 节点”,或“主要(primary) 节点”。主节点存放权威数据,而且通常负责处理数据更新操作。
  • 其余节点都叫“从(slave) 节点”,或“次要(secondary) 节点”,和主节点保持同步,负责读取操作 。

在需要频繁读取数据集的情况下,“主从复制”(master-slave replication) 有助于提升数据访问性能

  • 新增更多从节点的方式来进行水平扩展,就可以同时处理更多数据读取请求,并且能保证将所有请求都引导至从节点
  • 在写入操作特别频繁的场合,**数据库仍受制于主节点处理更新,以及向从节点发布更新的能力 **

“主从复制” 可以增强“读取操作的故障恢复能力”(read resilience)

  • 万一主节点出错了,那么从节点依然可以处理读取请求
  • 主节点出错之后,除非将其恢复,或另行指派新的主节点,否则数据库就无法处理写入操作。
  • 在主节点出错之后,由于拥有内容与主节点相同的从节点,很快就能指派一个从节点作为新的主节点,从而具备故障恢复能力
  • 主节点可以手工指派,也可自动选择。

“数据的不一致性”

  • 网络延迟或者主从负载不一致,导致从库落后于主库,无法及时反映主库的最新状态
  • 主从参数设置不一致,导致从库无法正确地解析或者执行主库的日志
  • 主从复制过程中出现错误或者异常,导致从库无法继续同步主库的日志

image.png

对等复制

“对等复制” 它没有“主节点”这一概念。所有“副本”(replica) 地位相同,都可以接受写入请求,而且丢失其中一个副本,并不影响整个数据库的访问
image.png

结合主从复制与分片

如果同时使用“主从复制”与“分片”,那么就意味着整个系统有多个主节点,然而对每项数据来说,负责它的主节点只有一个
根据配置需要,同一个节点既可以做某些数据的主节点,也可以充当其他数据的从节点,此外,也可以指派全职的主节点或从节点
image.png

结合对等复制与分片

使用列族数据库时,经常会将“对等复制”与“分片”结合起来。
数据可能分布于集群中的数十个或数百个节点上。在采用“对等复制”方案时,一开始可以用“3”作为复制因子(replication factor), 也就是把每个分片数据放在3个节点中。一旦某个节点出错,那么它上面保存的那些分片数据会由其他节点重建
image.png

ch67 分布式模型中的一致性

更新一致性(写入冲突和读写冲突)

当两个客户端试图同时修改一份数据时,会发生“写入冲突”。而当某客户端在另一个客户端执行写入操作的过程中读取数据时,则会发生“读写冲突”。

  • 悲观方式以锁定数据记录来避免冲突
    • “写入锁” (write lock)
  • 乐观方式则在事后检测冲突并将其修复:假设冲突的可能性很低,所以不会在更新前加锁或者检查数据的版本,而是在更新后检查是否有冲突发生,如果有则进行修复。
    • “条件更新”( conditional update),任意客户在执行更新操作之前,都要先测试数据的当前值和其上一次读入的值是否相同。如果数据的当前值和旧值不匹配,说明有其他客户端修改了数据,那么更新就会失败,否则更新就会成功
    • 保存冲突数据 。用户自行“合并”(merge)或 “自动合并”(面向特定领域),用于分布式版本控制系统

并发编程涉及一个根本问题,那就是在安全性(避免“更新冲突”之类的错误)与响应能力(liveness,快速响应客户操作)之间权衡。“悲观方式”通常会大幅降低系统响应能力,以致无法满足需求。而且它还有出错的危险:采用“悲观方式”处理并发问题,通常会导致“死锁”( deadlock),这一情况既难于防范,也不易调试。

保存冲突数据是一种修复冲突的方法,它不会覆盖或者丢弃任何客户端的更新,而是将冲突的数据都保存下来,让用户自己决定如何合并。例如,如果两个客户端同时修改了一个文档的不同部分,那么保存冲突数据的方法就会将两个版本的文档都保留下来,让用户自己选择或者编辑合并后的文档。
“自动合并”是一种特殊的保存冲突数据的方法,它针对一些特定领域的数据,可以根据一些规则或者算法自动地合并冲突的数据。例如,如果两个客户端同时修改了一个购物车的内容,那么自动合并的方法就可以将两个版本的购物车合并成一个,包含了所有客户端添加或者删除的商品。

逻辑一致性

“图数据库”常常和关系型数据库一样,也支持ACID事务。
面向聚合的数据库通常支持“原子更新”( atomic update),但仅限于单一聚合内部

  • “一致性” 可以在某个聚合内部保持,但在各聚合之间则不行
  • 在执行影响多个聚合的更新操作时,会留下一段时间空档,让客户端有可能在此刻读出逻辑不一致的数据
  • 存在不一致风险的时间长度就叫“不一致窗口”(inconsistency window)。

image.png
上述例子中的一致性就叫“逻辑一致性”(logical consistency),也就是要确保不同的数据项放在一起,其含义符合逻辑
为了避免“读写冲突”造成的“逻辑不一致”,关系型数据库支持“事务”这一概念。
若是Martin将两个写入步骤封装到一个事务之中,则系统能够确保Pramod所读出的那两项数据,要么都是更新之前的值,要么都是更新之后的值。

复制一致性

“复制一致性”(replication consistency)。要求从不同副本中读取同一个数据项时,所得到的值相同
image.png

  • 在分布式系统中,如果某些节点收到了更新数据,而另外一些节点却尚未收到,那么这种情况就视为“读写冲突”
  • 若写入操作已经传播至所有节点,则此刻的数据库就具备“最终一致性”( eventually consistent)
  • 复制不一致性带来的“不一致窗口”,在考虑网络环境后,会比单一节点导致的“不一致窗口”长的多
  • 在任意时刻,节点中都可能存在“复制不一致”(replication inconsistency)问题,然而只要不再继续执行其他更新操作,那么上一次更新操作的结果最终将会反映到全部节点中去。过期的数据通常称为“陈旧”( stale)数据,这提醒我们:缓存也算一种“复制”形式,尤其在“主从式分布模型”中,更是如此。
  • 不一致性窗口对应用的影响不同

image.png

照原样读出所写内容的一致性

“照原样读出所写内容的一致性”(read-your-writes consistency) ,在执行完更新操作之后,要能够立刻看到新值。
在具备“最终一致性” 的系统中,可以提供“会话一致性”( session consistency) :在用户会话内部保持“照原样读出所写内容的一致性”

  • 使用“黏性会话”(sticky session),即绑定到某个节点的会话(这种性质也叫做“会话亲和力”,session affinity)。
    • “黏性会话”可以保证,只要某节点具备“照原样读出所写内容的一致性”,那么与之绑定的会话就都具备这种特性了。
    • “黏性会话”的缺点是,它会降低“负载均衡器”( load balancer)的效能
  • 使用“版本戳”(version stamp),并确保同数据库的每次交互操作中,都包含会话所见的最新版本戳。服务器节点在响应请求之前必须先保证,它所含有的更新数据包含此版本戳。

使用“黏性会话”和“主从复制”来保证“会话一致性”时,由于读取与写入操作分别发生在不同节点,那么想保证这一点会比较困难。

  • 方法一:将写入请求先发给从节点,由它负责将其转发至主节点,并同时保持客户端的“会话一致性”。
  • 方法二:在执行写入操作时临时切换到主节点,并且在从节点尚未收到更新数据的这一段时间内,把读取操作都交由主节点来处理。

ch68 放宽“一致性”和“持久性”约束

放宽一致性约束

使用事务保障一致性

使用“事务”达成强一致性
引入放松“隔离级别” ( isolation level)的功能,以允许查询操作读取尚未提交的数据。

  • 读未提交,一个事务可以读取另一个未提交事务的数据。脏读
  • 读已提交,一个事务要等另一个事务提交后才能读取数据。不可重复读
  • 可重复读,在开始读取数据(事务开启)时,不再允许修改操作。幻读
  • 可串行化,事务串行化顺序执行。严格一致性,效率是一个问题

事务的问题

在并发不大的前提下,是否需要事务
在数据较多的情况下,为了让应用性能符合用户要求,它们必须弃用“事务”
尤其在需要引入分片机制时,更是如此
在分布式应用中,如事务的业务范围涉及多个以网络连接的参与者。其规模、复杂度和波动性均导致无法使用事务进行良好描述

很多系统已经彻底弃用“事务”了,因为它们对性能的影响实在太大。 有两种不采用事务的使用方式。在数据规模较小的情况下,MySQL
比较流行,那时它还不支持事务处理。许多网站喜欢MySQL所带来的高速访问能力,并且不准备再使用事务了。在数据较多的情况下,像eBay Pritchett]这种非常大的网站,为了让网站性能符合用户要求,它们必须弃用“事务”,尤其在需要引入分片机制时,更是如此。就算没有这些限制,很多应用程序构建者也需要同远程系统交互,而那些系统无法合理地容纳于事务范围之内,所以说,在企业级应用程序中,经常需要更新事务范围之外的数据。

CAP定理

CAP定理:给定“一致性”(Consistency)、“可用性”(Availability)、“分区耐受性”( Partition tolerance) 这三个属性,我们只能同时满足其中两个属性。

  • “一致性”
  • “可用性”,如果客户可以同集群中的某个节点通信,那么该节点就必然能够处理读取及写入操作。
  • “分区耐受性” ,如果发生通信故障,导致整个集群被分割成多个无法互相通信的分区时(这种情况也叫“ 脑裂”,split brain),集群仍然可用。

image.png

CA系统

CA系统,也就是具备“一致性”(Consistency)与“可用性”(Availability), 但却不具备“分区耐受性”的系统
大多数关系型数据库都是CA系统
CA集群

  • 无法保证“分区耐受性”,这使得一旦“分区”发生,所有节点必须停止运作
  • CAP中的,可用性定义为“系统中某个无故障节点所接收的每一条请求, 无论成功或失败,都必将得到响应。”
  • 介于此时所有节点均为故障节点,不违反CAP中的“可用性”

    从理论上说,也存在“CA集群”。然而,这意味着,一旦集群中出现“分区”,所有节点都将无法运转,如此一来,客户端就无法与任意一个节点通信了。按照“可用性”一词的常规定义来看,该系统此时缺乏“可用性”,然而如果按照“CAP定理”中“可用性”一词的特殊含义来解释,则会令人困惑。“CAP定理”将“可用性”一词定义为“系统中某个无故障节点所接收的每一条请求,无论成功或失败,都必将得到响应。”[Lynch and Gilbert]。所以按照这个定义来看,发生故障且无法响应客户请求的节点,并不会导致系统失去“CAP定理”所定义的那种“可用性”。

CAP定理的现实含义

尽管“CAP定理”经常表述为“三个属性中只能保有两个”,实际上当系统可能会遭遇“分区”状况时(比如分布式系统),需要在“一致性”与“可用性”之间进行权衡。

  • 这并不是个二选一的决定,通常来说,我们都会略微舍弃“一致性”,以获取某种程度的“可用性”
  • 这样的系统,既不具备完美的“一致性”,也不具备完美的“可用性”
  • 但是能够满足需要

举例

缺乏可用性

假设Martin与Pramod都想预订某旅馆的最后一间客房,预订系统使用“对等式分布模型”,它由两个节点组成Martin 使用位于伦敦的节点,而Pramod使用位于孟买的节点。若要确保一致性,那么当Martin要通过位于伦敦的节点预订房间时,该节点在确认预订操作之前,必须先告知位于孟买的节点。
两个节点必须按照相互一致的顺序来处理它们所收到的操作请求
此方案保证了“一致性”,但是假如网络连接发生故障,那么由故障导致的两个“分区”系统,就都无法预订旅馆房间了,于是系统失去了“可用性”。

改善可用性

指派其中一个节点作为某家旅馆的“主节点”,确保所有预订操作都由“主节点”来处理。

  • 假设位于孟买的节点是“主节点”,那么在两个节点之间的网络连接发生故障之后,它仍然可以处理该旅馆的房间预订工作,这样Pramod将会订到最后一间客房
  • 位于伦敦的用户看到的房间剩余情况会与孟买不一致,但是他们无法预订客房,于是就出现了“更新不一致”现象。
  • Martin可以和位于伦敦的节点通信,但是该节点却无法更新数据。

于是出现了“可用性”故障(availability failure)
这种在“一致性”与“可用性”之间所做的权衡,能正确处理上述特殊状况。

进一步改善

让两个“分区”系统都接受客房预订请求,即使在发生网络故障时也如此。
这种方案的风险是,Martin和Pramod有可能都订到了最后一间客房。然而,根据这家旅馆的具体运营情况,这也许不会出问题:

  • 通常来说,旅行公司都允许一定数量的超额预订,这样的话,如果有某些客人预订了房间而最终没有人住,那么就可以把这部分空余房间分给那些超额预订的人了
  • 与之相对,某些旅馆总是会在全部订满的名额之外多留出几间客房,这样万一哪间客房出了问题,或者在房间订满之后又来了一位贵宾,那么旅馆可以把客人安排到预留出来的空房中
  • 还有些旅馆甚至选择在发现预订冲突之后向客户致歉并取消此预订。

该方案所付出的代价,要比因为网络故障而彻底无法预订的代价小。

写入不一致

购物车是允许“写入不一致”现象的一个经典示例

  • 即使网络有故障,也总是能够修改购物车中的商品。
  • 这么做有可能导致多个购物车出现
  • 而结账过程则会将两个购物车合并,具体做法是,将两个购物车中的每件商品都拿出来,放到另外一个购物车中,并按照新的购物车结账。
  • 这个办法基本上不会出错,万一有问题,客户也有机会在下单之前先检视一下购物车中的东西。

一致性与延迟的取舍

在权衡分布式数据库的“一致性”时,与其考虑如何权衡“一致性”与“可用性”,不如思考怎样在“一致性”与“延迟”(latency)之间取舍。

  • 参与交互操作的节点越多,“一致性”就越好
  • 然而,每新增一个节点,都会使交互操作的响应时间变长
  • “可用性”可以视为能够忍受的最大延迟时间,一旦延迟过高,我们就放弃操作,并认为数据不可用
  • 这样一来,就和“CAP定理”对“可用性”所下的定义相当吻合了

放宽持久性约束

“一致性”的关键在于,将请求序列化,使之成为原子的(Atomic)、相互隔离的(Isolated)“工作单元”( work unit)。
“持久性”:如果某个数据库大部分时间都在内存中运行,更新操作也直接写入内存,并且定期将数据变更写回磁盘,那么,它就可以大大提高响应请求的速度了。这种做法的代价在于,一旦服务器发生故障,任何尚未写回磁盘的更新数据都将丢失

持久性权衡

数据库大部分时间都在内存中运行,更新操作也直接写入内存,并且定期将数据变更写回磁盘

  • 可以大大提高响应请求的速度。
  • 代价在于,一旦服务器发生故障,任何尚未写回磁盘的更新数据都将丢失。

多用户的“会话状态”信息会话

  • 数据就算丢失,与应用系统效率相比,也不过是个小麻烦。这时可以考虑非持久性写入操作”(nondurable write)。
  • 可以在每次发出请求时,指定该请求所需的持久性。从而,把某些极为重要的更新操作立刻写回磁盘。

捕获物理设备的遥测数据(telemetric data)。就算最近的更新数据可能会因为服务器发生故障而丢失,也还是选择把快速捕获数据放在首位。

分布模型中“持久性”的权衡

如一个节点处理完更新操作之后,在更新数据尚未复制到其他节点之前就出错了,那么则会发生“复制持久性”(replication durability) 故障。
假设有一个采用“主从式分布模型”的数据库,在其主节点出错时,它会自动指派一个从节点作为新的主节点。

  • 若主节点发生故障,则所有还未复制到其他副本的写入操作就都将丢失
  • 一旦主节点从故障中恢复过来,那么,该节点上的更新数据就会和发生故障这段时间内新产生的那些更新数据相冲突
  • 我们把这视为一个“持久化”问题,因为主节点既然已经接纳了这个更新操作,那么用户自然就会认为该操作已经顺利执行完,但实际上,这份更新数据却因为主节点出错而丢失了

解决方案:

  1. 不重新指派新的主节点
    1. 在主节点出错之后迅速将其恢复
  2. 确保主节点在收到某些副本对更新数据的确认之后,再告知用户它已接纳此更新
    1. 从节点发生故障时,集群不可用
    2. 拖慢更新速度

与处理“持久性”的基本手段类似,也可以针对单个请求来指定其所需的持久性。

ch69 仲裁

写入仲裁

image.png

读取仲裁

image.png
想要知道更好的一致性,需要访问更多的节点
想要更好的可用性,需要越方便越好
R > N - W = 还没有来得及更新的节点数

复制因子

复制因子不大于节点数,是为了“集群”
image.png
3个节点如果有一个出错,还有一个节点可以读,一个节点可以写
如果一次性出错2个,那么就无法读和写了。
如果设置复制因子为7,可允许出错的节点增加,但是执行速度变低

实际情况

image.png
R和W总是相互平衡的

ch70 版本戳

AT:原子性事务-绝大数是用来避免错误,进行回滚。如果某些事务占据资源多,占用时间长,会阻碍并发。
BA:商业活动
image.png
用业务流程上的补充完成“回滚”,而不是像事务一样all or nothing-在数据库的支持下完成回滚

ch71 键值数据库

概念

键值数据库(key-value store)是一张简单的哈希表(hash table),主要用在所有数据库访问均通过主键(primary key)来操作的情况下

  • 可把此表想象成传统的“关系” 该关系有两列:ID与NAME
  • ID列代表关键字,NAME列存放值。NAME列仅能存放String型的数据。
  • 应用程序可提供ID及VALUE值,并将这一键值对持久化
  • 假如ID已存在,就用新值覆盖当前值,否则就新建一条数据。

image.png

  • 键值数据库是最简单的NoSQL数据库。
  • 客户端可以根据键查询值,设置键所对应的值,或从数据库中删除键。
  • “值”只是数据库存储的一块数据而已,它并不关心也无需知道其中的内容
  • 应用程序负责理解所存数据的含义
  • 由于键值数据库总是通过主键访问,所以它们一般性能较高,且易于扩展。

流行的键值数据库有:Riak、Redis(数据结构服务器)、 Memcached DB及其变种、Berkeley DB、HamsterDB (尤其适合嵌入式开发) 、 Amazon DynamoDB (不开源)和Project Voldemort (Amazon DynamoDB的开源实现)。

数据结构服务器

  • 在键值数据库中,所存储的聚合不一定是领域对象(domain object),也可以拥有通用数据结构
  • Redis能够存储list、set、hash 等数据结构,可以支持“获取某个范围内的数值”(range)、“求差集”(diff)、“求并集”( union)、 “求交集”( intersection) 等操作
  • 这些功能使数据库的用途变得比标准键值数据库更多

单一存储区

image.png
将各类对象(也就是聚合)全部存放在一个“存储区”中,其缺点是:“存储区”中可能要存放类型不同的多个聚合,这增加了关键字冲突的几率

如果一个存储区中既有用户聚合,又有订单聚合,那么可能出现用户ID和订单ID相同的情况,这就导致了关键字冲突。关键字冲突会影响数据的正确性和一致性,因为通过键查询时,可能返回错误的或多余的数据。为了避免关键字冲突,可以采用以下一些方法:

  • 为不同类型的聚合使用不同的命名空间或前缀,例如 user:123 和 order:123。
  • 为键生成全局唯一的标识符(GUID),例如 8f14e45f-ea4d-439a-8da3-cb556e7c9b53。
  • 为存储区设置唯一性约束或主键约束,防止插入重复的键。

还有一种办法是把对象名放在键名后面,例如:288790b8a421_ userProfile, 这样就可用它查出所需的单个对象了
image.png

领域存储区

“领域存储区”(domain bucket)来存放特定数据
客户端驱动程序可以对其执行“序列化”(serialization) 与“反序列化”(deserialization) 操作将跨越多个“存储区”的数据分割成对象,将之存放在“领域存储区”或不同的“存储区”中,这样一来,无需改变关键字的命名方式,即可读出所需对象
存放表达相同含义的不同聚合方案,以应对多种不同应用的需求
效率及数据不一致性问题

领域存储区中的数据可以是复杂的对象,而不仅仅是简单的键值对。
对象可以包含多个属性和关联,这些属性和关联可能分布在不同的存储区中。

为了在存储和读取对象时,保持对象的完整性和一致性,客户端驱动程序可以对对象进行序列化和反序列化的操作。

序列化是指将对象转换为字节流或字符串的过程,反序列化是指将字节流或字符串还原为对象的过程。

举例来说,如果一个电商公司的键值数据库中有一个领域存储区,用于存放用户对象。
用户对象包含用户ID、用户名、密码、邮箱、收货地址等属性,以及与订单、商品等其他对象的关联。
这些属性和关联可能分散在不同的存储区中,例如用户ID和用户名在一个存储区,密码和邮箱在另一个存储区,收货地址在第三个存储区,订单和商品在第四个存储区等。
当客户端驱动程序要将一个用户对象存入数据库时,它可以对用户对象进行序列化,将其转换为一个字节流或字符串,然后根据一定的规则,将其分割成多个键值对,分别存放在不同的存储区中。
当客户端驱动程序要从数据库中读取一个用户对象时,它可以根据用户ID或其他标识符,从不同的存储区中查询出相应的键值对,然后对其进行反序列化,将其还原为一个完整的用户对象。

一致性

只有针对单个键的操作才具备“一致性”,因为这种操作只可能是“获取”、“设置”或“删除”。
由于数据库无法侦测数值改动, “乐观写入”(optimistic write)功能的实现成本太高。
分布式键值数据库,用“最终一致性模型” 实现“一致性”。
两种解决“更新冲突”的办法:

  1. 采纳新写入的数据而拒绝旧数据
  2. 将两者(或存在冲突的所有数据)返回给客户端,令其解决冲突

在创建“存储区”时设置与一致性有关的选项。

  • 若想提高数据一致性, 可以规定:执行完写入操作后,只有当存放此数据的全部节点一致将其更新,才认定该操作生效。
    • 显然降低了集群的写入效率
  • 若想提高写入冲突或读取冲突的解决速度,可在创建“存储区”时设置为数据库接纳最新的写入操作,而不再创立“旁系记录”(sibling)

事务

不同类型的键值数据库,其“事务”规范也不同,实现“事务”的方式各异。
一般说来,无法保证写入操作的“一致性”。
Riak在调用写入数据的API时,它使用W值与复制因子来实现“仲裁”。
假设某个集群的复制因子是5,而W值为3。
在写入数据时,必须有至少3个节点汇报其写入操作已顺利完成,数据库才会认为此操作执行完毕。
由于N等于5而W是3,所以集群在两个节点(N-W=2) 故障时仍可执行写入操作,不过,此时我们无法从那些发生故障的节点中读取某些数据。

查询功能

所有键值数据库都可以按关键字查询。它们的查询功能基本上仅限于此。
如果希望根据“值列”(value column)的某些属性来查询,那么无法用数据库完成此操作
应用程序需要自己读出值,并判断其属性是否符合查询条件。

订单ID作为键,订单对象作为值,存放在一个键值对中。如果希望根据订单ID来查询订单对象,那么可以直接用键值数据库完成此操作,只需要输入订单ID,就可以快速地返回对应的订单对象。但是,如果希望根据订单对象的某些属性来查询,例如查询总价大于1000元的订单,或者查询下单时间在某个时间段内的订单,那么无法用键值数据库完成此操作。应用程序需要自己读出所有的订单对象,并判断其属性是否符合查询条件,这样做会消耗大量的时间和资源。

如果不知道关键字该怎么办?

  • 大部分数据库都不提供全部主键列表,即便提供了,获取关键字列表并查询其值的操作也很烦琐
  • 某些键值数据库支持数值搜寻,以解决此问题
  • 通过API、HTTP(浏览器、Curl等),操作键值数据库

image.png

image.png

image.png

键名的设计

使用键值数据库时,通过某种算法生成键

  • 使用用户信息(例如ID、电子邮件地址等)、时间戳等值,生成键

键值数据库非常适合保存会话(用会话ID作为键)、购物车数据、用户配置等信息

数据结构

键值数据库并不关心键值对里的值。它可以是二进制块、文本、JSON、XML等。
可在HTTP请求中用Content-Type指定数据类型
实质上是由应用判定其内容

可扩展性

很多键值数据库都可用“分片”技术扩展。采用此技术后,键的名字就决定了负责存储该键的节点。

  • 假设按照键名的首字母“分片”。如果键名是f4b19d79587d,那么由于其首字母为f,所以存放它的节点就与存放ad9c7a396542这个键的节点不同。
  • 当集群中的节点数变多时,这种“分片”设定可提高效率。

“分片”也会引发某些问题。假如存放首字母为f的键所用的那个节点坏了,那么其上的数据将无法访问,而且也不能再写入其他键名首字母为f的新数据了。

可以控制“CAP定理” 中的参数: **N (存放键值对的副本节点数)、R (顺利完成读取操作所需的最小节点数)和W (顺利完成写入操作所需的最小节点数)**。

  • 假设集群有5个节点。将N设为3,意思就是所有数据都至少要复制到3个节点中,将R设为2,意思是GET请求要有两个节点应答,才能成功,将W设为2,意思是PUT请求必须写入两个节点,才算执行完毕。

可以利用这些设置来微调读取及写入操作所能容忍的故障节点数。应该按照应用的需要来改变这些值,以**提升数据库的“可读能力”(read availability) 及“可写能力”(write availability)**。通常应该根据“一致性”需求来确定W值。
创建“存储区”时可设定上述各参数的默认值。

适用案例

存放会话信息

通常来说,每一次网络会话都是唯一的,所以分配给它们的sessionid值也各不相同。
如果应用程序原来要把sessionid存在磁盘上或关系型数据库中,那么将其迁移到键值数据库之后,会获益良多

  • 因为全部会话内容都可以用一条PUT请求来存放,而且只需一条GET请求就能取得。
  • 由于会话中的所有信息都放在一个对象中,所以这种“单请求操作”(single-request operation) 很迅速。

    网络会话是指用户与服务器之间的一次交互过程,从用户发起请求到服务器返回响应为止。网络会话可以用来保存用户的状态信息,例如登录状态、购物车内容、浏览历史等。为了实现网络会话,服务器会给每个用户分配一个唯一的标识符,称为sessionid,通常会通过cookie或URL参数的方式传递给用户。用户在每次请求时,都会带上sessionid,以便服务器识别用户的身份和状态

用户配置信息

用户配置信息,几乎每位用户都有userId、username或其他独特的属性,而且其配置信息也各自独立,诸如语言、颜色、时区、访问过的产品等。
这些内容可全部放在一个对象里,以便只用一次GET操作即获取某位用户的全部配置信息。
同理,产品信息也可如此存放。

购物车数据

购物车数据,电子商务网站的用户都与其购物车相绑定。
由于购物车的内容要在不同时间、不同浏览器、不同电脑、不同会话中保持一致,所以可把购物信息放在value属性中,并将其绑定到userid这个键名上。

不适用场合

  • 数据间关系
    • 如果要在不同数据集之间建立关系,或是将不同的关键字集合联系起来,那么即便某些键值数据库提供了“链接遍历”等功能,它们也不是最佳选择。
  • 含有多项操作的事务
    • 如果在保存多个键值对时,其中有一个关键字出错,而又需要复原或回滚其余操作,那么键值数据库就不是最好的解决方案。
  • 查询数据
    • 如果要根据键值对的某部分值来搜寻关键字,那么键值数据库就不是很理想了。我们**无法直接检视键值数据库中的值,除非使用某些“检索引擎”(indexing engine)**。
  • 操作关键字集合
    • 由于键值数据库一次只能操作一个键,所以它无法同时操作多个关键字。假如需要操作多个关键字,那么最好在客户端处理此问题。

ch72 文档数据库

概念

“文档”( document)是文档数据库中的主要概念。

  • 其格式可以是XML、JSON、BSON等。
  • 文档具备自述性(self-describing),呈现分层的树状数据结构(hierarchical tree data structure),可以包含映射表、集合和标量值。

文档彼此相似,但不必完全相同。
文档数据库所存放的文档,就相当于键值数据库所存放的“值”
文档数据库可视为其值可查的键值数据库
image.png

  • 文档数据库中,放在同一“集合”内的各文档的“数据模式”(the schema of the data)可以不同
  • 关系型数据库中,表格中每行数据的模式都要相同。

文档中可以嵌套数组等基本数据类型,也可以将“子文档”(child document) 以“子对象”(subobject) 的形式嵌入主文档。

  • 由于没有“数据模式”约定,文档数据库的文档中无需空属性,若其中不存在某属性,就假定该属性值未设定或与此文档无关。向文档中新增属性时,既无需预先定义,也不用修改已有文档内容。
  • 关系型数据库中,需要定义表中的每一列,而且若某条记录中的某列没有数据,则要将其留空(empty) 或设为null。

流行的文档数据库有: MongoDB、 CouchDB、Terrastore、OrientDB、RavenDB和Lotus Notes。

一致性

通过配置“副本集”(replica set) 实现“复制”,以提供较高的“可用性”
规定写入操作必须等待所写数据复制到全部或是给定数量的从节点之后,才能返回。从而指定数据库的“一致性”强度。

  • 在只有一台服务器时如果指定w为“majority”,那么写入操作立刻就会返回,因为总共只有一个节点。
  • 假设“副本集”中有三个节点,则写入操作必须在至少两个节点上执行完毕,才会视为成功
  • 提升w值可以增强“一致性”,但是会降低写入效率,因为写入操作必须在更多的节点上完成才行。
  • 也可以增加“副本集”的读取效率:设置slaveOk选项之后,就可以于从节点中读取数据了。
  • 参数既可设置到整个”连接”、“数据库”、“集合”之上,也可针对每项操作独立设置。

事务

从传统的关系型数据库角度讲,“事务”一词意味着我们可以先用insert、update或delete等命令操作不同的表,然后用commit提交修改或以rollback命令回滚NoSQL数据库通常没有这些机制:其写入操作要么成功,要么失败。
“单文档级别”(single-documentlevel)的“事务”叫做“原子事务”( atomic transaction)。
可以用不同级别的WriteConcern参数来确保各种安全级别的写入操作

  • 在默认情况下,所有写入操作都将顺利执行。
  • 以WriteConcern.REPLICAS_SAFE为参数写入,即可确保该操作至少要写入两个节点才算成功。
  • 在写日志条目(log entry)时,就可使用最低的安全级别,也就是WriteConcern.NONE。

可用性

文档数据库可以用主从式数据复制技术来增强“可用性”。多个节点都保有同一份数据,即便主节点故障,客户端也依然能获取数据。应用程序代码一般不需检测主节点是否可用。
MongoDB通过“副本集”实现“复制”,以提供较高的“可用性”。副本集中至少有两个节点参与“异步主从式复制”(asynchronous master-slave replication)。

  • “副本集”通常用于处理“数据冗余”( data redundancy)、“自动故障切换”( automated failover)、 “读取能力扩展”(read scaling)、“无需停机的服务器维护( server maintenance without downtime)和“灾难恢复”(disaster recovery)等事项。

应用程序的写入或读取操作都针对主节点。建立连接后,应用程序只需要同“副本集”中的一个节点相连即可(是不是主节点无所谓),数据库会自动找到其余节点。若主节点故障,则数据库驱动会同“副本集”中新选出的主节点联系。应用程序不用处理通信错误,也无需干预主节点的选拔准则。
副本集在其内部选举“主”(master)节点,或 “主要”(primary)节点。假定所有节点投票权相同,其中某些节点可能会因为**距离其他服务器较近,或具有更多运行内存(RAM)**等因素而获得更多选票。用户也可以为节点指定一个值在0 ~ 1000之间的优先级( priority)来影响选举过程。
所有请求都由主节点处理,而其数据会复制到从节点。若主节点故障,则“副本集”中剩下的节点就会在其自身范围内选出新的主节点,所有后续请求就交由新的主节点处理,从节点也开始从新的主节点处获取数据。
当原来的主节点从故障中恢复时,它会作为从节点重新加入,并获取全部最新数据,以求与其他节点一致。
image.png
(副本集内部也有自己的主节点)

查询功能

文档数据库可以查询文档中的数据,而不用像键值数据库那样,必须根据关键字获取整个文档,然后再检视其内容。
CouchDB:可用“物化视图”(materialized view)或“动态视图”(dynamic view)实现复杂的文档查询。
MongoDB支持一种JSON格式的查询语言由于文档是“聚合对象”(aggregated object),所以用带子对象的字段查询待匹配的文档非常方便。

可扩展性

在不将数据库进行迁移的前提下,向其中新增节点或修改其内容。
增加更多的“读取从节点”(read slave),将读取操作导引至从节点上,这样就可以扩展数据库应对频繁读取的能力了。
假设某个应用程序的读取操作很频繁,可向“副本集”中加入更多从节点,并在执行读取操作时设定slaveOk标志,以提升集群的读取能力。完成读取操作的横向扩展。
image.png
如果想扩展写入能力,可以把数据“分片” 。
“分片”与关系型数据库的“分区”类似,

  • “分区”是根据某列的值,例如状态或年份,将数据分割开。关系型数据库的“分区”通常位于同一节点,所以客户端应用程序只查询“基表”(base table)就好,不需查询某个特定分区,关系型数据库会根据查询内容搜索适当的分区并返回数据。
  • “分片”操作也根据特定字段来划分数据,然而那些数据要移动到不同的Mongo节点中。为了让各“分片”的负载保持均衡,需要在节点之间动态转移数据。向集群中新增更多节点,并提高可写入的节点数,就能横向扩展其写入能力。

“分片”的关键字很重要。

  • 按照客户名字(first name)来分隔,可确保将数据平衡地散布在各个“分片”上,以获得较好的写入效率。
  • 如果想把 “分片”放在距离用户近的地方,那么可以以用户位置来分片。按客户位置分片时,美国东海岸的全部用户数据都会放在居于东海岸的“分片”中,而所有西海岸的用户数据则将放在位于西海岸的“分片”中。

可以把每个”分片”都做成“副本集”,以提高其读取效率。
如果向已有的“分片集群”(sharded cluster)中再加一个新分片”,就可以把原来分布在3个“分片”中的数据打散到4个“分片”中。
在转移数据与底层设施重构的全过程中,虽说集群为了重新平衡“分片”负载而传输大量数据时性能也许会下降,但是应用程序却无需停止工作。
image.png
:::danger
分片和副本集有什么异同
:::

适用案例

事件记录

  • 在企业级解决方案中,许多不同的应用程序都需要记录事件。应用程序对事件记录各有需求。
  • 文档数据库可以把所有这些不同类型的事件都存起来,并作为事件存储的“中心数据库”(central data store)使用。
  • 如果事件捕获的数据类型一直在变,那么就更应该用文档数据库了。
  • 可以按照触发事件的应用程序名分片,也可以按照order_processed或customer_logged等事件类型“分片”

其他

  • 内容管理系统及博客平台

由于文档数据库没有“预设模式”(predefined schema),而且通常支持JSON文档,所以它们很适合用在“内容管理系统”(content management system)及网站发布程序上,也可以用来管理用户评论、用户注册、用户配置和面向Web文档(web-facing document)。

  • 网站分析与实时分析

文档数据库可存储实时分析数据。由于可以只更新部分文档内容,所以用它来存储“页面浏览量”(page view)或“独立访客数”( unique visitor)会非常方便,而且无需改变模式即可新增度量标准。

  • 电子商务应用程序

电子商务类应用程序通常需要较为灵活的模式,以存储产品和订单。同时,它们也需要在不做高成本数据库重构及数据迁移的前提下进化其数据模型。

不适用场合

  • 包含多项操作的复杂事务

文档数据库也许不适合执行“跨文档的原子操作”(atomic cross-document operation),虽然像RavenDB等文档数据库其实也支持此类操作。

  • 查询持续变化的聚合结构

灵活的模式意味着数据库对模式不施加任何限制。数据以“应用程序实体”(application entity)的形式存储。
如果要即时查询这些持续改变的实体,那么所用的查询命令也得不停变化(用关系型数据库的术语讲,就是:用JOIN语句将数据表按查询标准连接起来时,待连接的表一直在变)。
由于数据保存在聚合中,所以假如聚合的设计持续变动,那么就需要以“最低级别的粒度”( lowest level of granularity)来保存聚合了,这实际上就等于要统一数据格式了。在这种情况下,文档数据库也许不合适。

ch73 列族数据库

ch74 图数据库