万字笔记带你了解NoSQL数据库
这篇书评可能有关键情节透露
概念介绍
Nosql 数据库可分为四种类型
键值 文档 列族 图
阻抗失谐问题
关系模型把数据组织成"表/关系"和"行/元组"
SQL 操作所用及返回的数据是关系,元组是键值对。
内存中的数据组织形式比关系更丰富,想要保存在磁盘前必须将其转换成"关系"形式。(阻抗失谐)
"对象-关系映射框架"可以轻松解决阻抗失谐的问题
集群的出现
关系型数据库并不是设计给集群用的。
集群是扩大资源规模的一种横向扩充方式。
这样便引出了"分片"(shareding)的做法
最初始的分片,应用程序必须控制所有分片。查询、参照完整性、事务、一致性控制等操作也都无法以跨分片的方式执行。
Nosql 的出现
开源分布式的非关系型数据库。
不适用 SQL 开源项目,大多数 nosql 数据库都是在集群环境中运行 不需要使用"模式",不用事先修改结构定义 专注于那些集群上运行的"大数据"
NoSQL 崛起所产生的重要影响就是混合持久化。
聚合
聚合是"领域驱动设计"中的术语。即把一组相互关联的对象视为一个整体单元来操作,而这个单位就叫聚合。
用聚合为单位来复制和分片比较自然(集群状态)
JSON 格式是 NoSQL 领域常用的数据格式
争取在数据交互时尽量减少所访问的聚合个数(数据访问方式,与处理数据的思路有关)
"聚合无知"
如果在数据中明确包含聚合结构,可以根据此信息知道哪些数据要一起操作,并将这类数据放在同一个节点。
NoSQL 数据库不支持跨越多个聚合的 ACID 事务。每次只能在一个聚合结构上执行原子操作。
键值数据模型与文档数据模型
键值数据库的聚合不透明。要访问聚合内容,只能通过键查找。文档数据库可以用聚合中的字段查询(文档的内部结构),而不用获取全部内容。
列族存储
采用"大表格式数据模型"的数据库通常被称为"列族存储数据库"
写入操作执行得很少,但是经常需要一次读取若干行中的列。在这情况下,将所有行的某一组列作为基本数据存储单元,效果会更好。
两级聚合结构。第一个键(本身也是一个映射)通常代表行标识符,用于获取想要的聚合。第二个键,"二级值",可称作列。
每一个列都必须是某个列族的一部分,而且访问数据的单元也得是列。
面向列:每一个列族都定义了一种记录类型,其中每行都标识一条记录。可以理解成数据库中的大"行"是列族中每一个短行记录的串接。
"列族"体现了"列族数据库"二维映射这一特点,每个文档依然被视作独立单元。可以在任意行中添加任何列,并且行中可以有差异很大的"列"键。
宽行:列较多,但行间会较少次出现相同的列 瘦行:列不多,但行间会较多次出现相同的列
2
数据模型详解
大多数 NoSQL 数据库的关键特征在于使用聚合,而各种面向聚合的数据库对聚合的建模方法不同。
图数据库
基本数据模型:由边(arc)连接而成的若干节点(node)。在图数据库中遍历关系时速度快,其主要原因是图数据库会多花时间用于插入关系数据(空间换时间模式),以此来缩短遍历关系时所需的时间。
图数据库与面向聚合数据库的差异,在其重视数据间的"关系",导致这种图形数据库通常运行在单一的服务器上。为了使数据保持一致,ACID 事物需要涵盖多个节点与边。
无模式数据库
各种 NoSQL 数据库有个共同点,他们均不需要定义结构(模式),不需要约定每一个列存放数据的类型。在列族数据库中,任意列里面都可以随意存放数据,灵活性大。
无模式数据库也更容易处理"格式不一致的数据",即每条记录都拥有不同字段集(set of field)的数据。
劣势:不管数据库"无模式"到何种地步,总会存在"隐含模式",指在编写数据操作代码时,对数据结构所做的一系列假设。
从本质上说,无模式数据库把模式交由访问其数据的应用程序代码来处理,这更要求代码结构质量、逻辑。
物化视图
关系数据库有"视图"机制,在访问视图时,数据库会算出视图中的数据,封装展示。
而 NoSQL 数据库的物化视图指一种预发算好并缓存在磁盘中的视图。如果数据读取频繁,访问者对数据实时性要求不高,使用物化视图效率更高。
两种方式构建物化视图,根据业务需求选择:
一旦基础数据有变动,立即更新物化视图。适合读取的次数远多于写入,且想获得更为及时地数据的情况。 若不追求极致的实时性,可以定期通过批处理操作来更新。
构建数据存储模型
使用列族建模时,应按照查询需求而非写入需求来做,建模目的是便于查询。
映射化简 ( https://www.cnblogs.com/liufei1983/p/9439830.html ):一种在集群上执行并发计算所用的模式,通道-流式。
3
分布式模型
面向聚合数据库适合于横向扩展方向,至此聚合自然成了数据分布单元。
宽泛讲,数据分布有两条路径:复制(relication)与分片(shadring)。两者可同时采用,也可单一采用。
单一服务器
多数情况下,推荐最简单的分布形式,根本不分布。这样不用考虑使用其他方案时所需应对的复杂事务,简化工作量。
分片
数据的各个部分存放于不同的服务器中,以此实现横向扩展。
在理想情况下,不同节点会服务于不同的用户,他们进行一对一的通信。网络负载相当均衡地分布于各台服务器上。为了获得接近于这样的效果,必须保证同时访问的那些数据放在同一节点上,且节点必须排布好数据块。
若是能够确定聚合的访问者基本上处于某个地理范围内,那么可以把数据放得离访问者近一些。
现在很多的 NoSQL 数据库支持自动分片功能,能把数据访问引导适当的分片上,提升读取和写入的效率(对频繁写入提升不大)。
从实际角度看,若数据分布在不同节点且无备份,分片技术可能会降低数据库的错误恢复能力。
主从复制
复制指的是从节点与主节点同步的通信过程。
主节点存放权威数据,所有写入请求都由主节点处理,读取请求可由主节点处理,也可以交给从节点;从节点通常负责读操作,充当即时备份的角色。
"主从"以新增更多节点的方式进行水平扩展(读取能力),并能保证将所有请求引导到从节点,增强了"读取操作的故障恢复能力"~从升主。
数据库受制于主节点处理更新以及向节点发布更新的能力,因为必须考虑数据与状态的一致性。在写入频繁的场合,需要考虑是否采用主从。
对等复制
与主从模式类似,每个节点均可处理读写请求,但容易引发"写入冲突"的并发问题。如何解决写入操作的不一致问题:
不管何时写入数据,花费一定量的网络流量来协调,使得各副本之间总能相互协调,确保不冲突。 设法处理这些不一致的写入操作,例如把这些冲突的写入操作合并起来。
4
一致性
更新一致性
并发环境下,在更新操作顺序一致时,维护数据一致性所用的方式通常分为"悲观方式"与"乐观方式"。前者避免发生冲突,后者先让冲突发生,检测冲突并对发生冲突的操作排序。
悲观式:通常采用写入锁,需确保某一时刻只有一方能获得这个锁。保证安全性,会大幅度降低系统响应能力,可能会导致死锁问题。 乐观式:通常采用条件更新,执行更新操作前先测试数据的当前值是否与上一次读入的值相同(CAS)。
上述是在"顺序一致性"的前提下进行,在分布式环境中,考虑将两份更新数据都保存下来,并标注出它们存在冲突的点。处理冲突时,必须以某种方式将冲突操作"合并"。
读取一致性
数据库保持更新一致性,只是解决部分问题,当前仍无法保证用户所提交的访问请求总是能得到内容一致的响应。
"逻辑一致性",要确保不同的数据项放在一起,其含义符合逻辑,即不存在"读写不一致"的现象。在 NoSQL 数据库中仅限于单一聚合内部,也就是说"逻辑一致性"可以在某个聚合内部保持,但在各聚合之间不支持。
在执行影响多个聚合的更新操作时,会留下一个时间空挡(不一致窗口),此时会有同步不及时的逻辑(数据)不一致现象。
当然最终更新操作会传播到全部节点,这种情况通常被称为"最终一致性"。在任意时刻,节点都可能存在"复制不一致"问题,但只要期间不再执行更新操作,上一次更新的结果最终会同步到全部节点中。
会话一致性:在用户会话内部保持"照原样读出所写内容的一致性",即在执行完更新操作后,紧接着必须能看到更新之后的值。通过版本戳控制,确保同数据库的每次交互操作中,都包含会话所见的最新版本戳。服务器节点在响应请求之前要保证,它所含有的更新数据包含此版本戳。
CAP 定理
在 NoSQL 领域中,通常认为"CAP 定理",是需要放宽一致性约束的原因。
CAP 定理的基本表述是:给定"一致性"(Consistency,C)、"可用性"(Availabilty,A)、"分区耐受性"(Partition tolerance,P)三个属性,只能同时满足其中两个属性。作为分布式系统的理论,P 是必要的,所以要在 A 与 C 中做选择。===>当系统可能会遭遇"分区"状态时,我们需要在 C 和 A 之间进行权衡,综合处理来满足特定需求。
CAP 将"可用性"一词定义为"系统中某个无障碍节点所接收的每一条请求,无论成功失败,都会得到响应"。这可以视为能够忍受的最大延迟时间,一旦延迟过高,就放弃操作,并认为数据不可用。
NoSQL 系统具备"BASE 属性"(基本可用、柔性状态、最终一致性),ACID 与 BASE 之间存在多个逐渐过渡的权衡方案可选。
仲裁
假设某份数据需要复制到三个(复制因子)节点中。为了保证"强一致性",只需要超过半数的节点确认就可以。->写入仲裁。
只要从足够数量的节点中读出数据,即便在写入操作不具备"强一致性"的情况下,也可以实现具有'强一致性'的读取操作。
5
版本戳
面向聚合的 NoSQL 数据库支持聚合内部的原子操作,以聚合为单位更新数据,但依然需要考虑是否支持"事务"。
版本戳可以解决/缓解事务打开时间过长(无法封装在一个事务之内)的情况,尤其从'单服务器分布模型'迁移到多服务器时。
版本戳是一个字段,每当记录中的底层数据改变,其值也随之改变。读取数据时可以记下版本戳,这样的话,在写入数据之前,可以先检查一下数据版本是否已变。
/*在通过HTTP协议更新资源时,就涉及到一种实现方式etag。无论何时获取资源,服务器总会在响应信息的头部放置etag,用来标识资源的版本。如果想要更新此资源,那么可以采用"条件更新",把上次通过Get请求获取的etag提交给服务器。若服务器上的相应资源改变,提交的etag会与服务器的etag不匹配,这时会得到412状态码的响应。(412PreconditionFailed)服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。这个状态码允许客户端在获取资源时在请求的元信息(请求头字段数据)中设置先决条件,以此避免该请求方法被应用到其希望的内容以外的资源上。*/
如何构建版本戳
使用计数器,每当资源更新值+1。需要服务器生成,并且要有一个主节点来保证不同版本的计数器值不重复。 创建 GUID。不必担心重复,数值较大且无法直接比较来判断版本新旧。 根据资源内容生成哈希码。当哈希算法相同时,同一资源哈希值的内容,在任意节点上的哈希值一样。无法直接比较来判断版本新旧且冗长。 使用上次更新的时间戳。需保证集群内时钟同步且精度相同。 融合上述方式之几
上述方式适用于只有一个权威数据源的场景,由主节点负责生成版本戳,从节点必须使用主节点的版本戳。"对等式分布模型"因为没有统一设置版本戳的地方,故此在多节点环境中生成版本戳时需要改进。
6
映射-化简
起初因谷歌的 MapReduce 框架出名。映射-化简一词来源于"函数式编程语言"对集合的"映射"和"化简"操作。
基本"映射-化简"
"映射-化简"框架可以在每个节点上高效地创建"映射"任务,每个应用程序的映射函数各自独立。
最基本的方法便是将不同节点中所有"映射任务"的输出值都连接起来,并传递给"化简函数"即可。
第一步是映射,操作只限于一条记录。输入为某个聚合,输出值为特定的键值对。只需要函所依赖的数据都位于某个聚合内部。 第二步是化简,接受多个关键字相同的映射操作输出值为其输入参数,将其合并。化简函数可以操作所有具备同一关键字的数据。
因此,要执行"映射-化简"任务,只需要编写两个函数即可,框架会将所有键值对收集起来,把相同关键字下的数值汇聚成集合,并以此关键字与数值集合为参数,调用化简函数。
分区与归并
可以设法提升"映射-化简"的并发操作并减少数据传输量。
首先将'映射函数'的输出数据分区,并提高并发处理能力。这样的话,多个"化简函数"可以在各个"分区"里并发执行,最后将其结果合并起来。(这步可叫做混排,分区一词有时也称存储区/区域)。
归并函数可以把具备同一个关键词的所有数据合并成一个值,本质也是一种化简。事实上很多情况下也能这样做。
化简函数若想用作归并函数,还需具备一个特性:输出值必须与输入值的形式相匹配(不用转参数类型),满足该特性的化简函数被称为"可用作归并函数的化简函数"。
有了"可用作归并函数的化简函数",那么该框架不仅可安全地执行并发操作(同时化简多个分区),而且可用在不同的时间和地点相继化简同一分区内的数据,甚至能在"映射函数"还未执行完毕时开始归并。
两阶段"映射-化简"
如果"映射-化简"计算比较不咋,可以使用"管道及过滤器"(pepes-and-filters),即将某个阶段的输出数据作为下一阶段的输入数据。
将数据生成过程分解为多个"映射-化简"步骤后,编程难度降低。另一个好处是,它所输出的中间数据可以复用计算其他输出数据,节省执行速度的同时,能将中间记录放在数据库里以构成"物化视图"。
针对于各种查询请求,"映射-化简"操作的早期阶段所生成的数据可用作一些共有的计算操作中,同时提取到物化视图中,提供给下游服务使用。
增量式"映射-化简"
上述讨论是针对完整的"映射-化简"计算流程,也就是从原始输入数据开始,直至算出最终输出结果。
许多"映射-化简"计算,在计算过程中,新数据会不断涌入,为了保证输出的数据不过时必须重新执行计算流程。
所以通常将"映射-化简"计算组织成易于"增量更新"的形式。
映射阶段处理增量,必须重新执行"映射函数"。因为映射操作彼此隔离,故操作难度不高。 化简需要把较多的映射操作的数据汇聚起来,如果某个映射操作的输出数据改变了,那么必须再次化简。根据化简操作的"平行程度",可以减少重新化简时的计算量。假如把待化简的数据"分区",那么其数据未改变的"分区"就不需再化简,归并操作也相似。
需要明白自己使用的"映射-化简"框架是如何支持增量操作的。
7
键值数据库
从 API 角度看,键值数据库是最简单的 NoSQL 数据库。客户端可以根据键查询值,设置键所对应的值,或从数据库中删除键。
"值"只是数据库存储的一块数据,不关系也不需知道其中的问题;代码负责理解所存储数据的含义。
流行的键值数据库有:Riak、Redis(通常称为"数据结构服务器")、Memcached DB 以及其变种等等。键值数据库,其所存储的聚合不一定非要是"领域对象",任何数据结构都可以。
本章以 Riak 数据库为例,进行举例说明。
可以创建"存储区"存放特定数据。在 Riak 中叫领域存储区,客户端驱动程度可以对其执行"序列化"与"反序列化"操作。
将跨越多个"存储区"的数据分割成对象,将之存放在领域存储区或不同的存储区,这样不需改变关键字的命名方式,就可以读出所需对象。
键值数据库特性
讨论每一种NoSQL数据库的特性,都要了解"一致性"、"事务"、查询特性、数据结构及可扩展性。
Riak 分布式键值数据库,用"最终一致性模型"实现"一致性",其解决更新冲突的方法有二:
采纳新写入的数据而拒绝旧数据 将两者(或存在冲突的所有数据)返回客户端,令其解决冲突。
事务
Riak 采用"仲裁"概念解决写入操作的"一致性",W 值与复制因子实现。
查询功能
想要根据"列值"的某些属性查询,则无法用数据库完成此操作:应用程序需要自己读出值,并判断某属性是否符合查询条件。
副作用:如果不知道关键字的话,查询操作很麻烦。使用键值数据库时,精力要花在设计键名上。
数据结构
键值数据库并不关心键值对里的值。它可以是二进制、文本、JSON、XML 等。
可扩展性
很多键值数据库都可用"分片"技术扩展。采用此技术后,键的名字(往往)决定了负责存储该键的节点。当集群中节点数变多时,这种分片设定可提高效率。
同时"分片"也意味着,如果存放单一数据的节点出现故障,这部分访问会失效,也不能再写入新数据。
所以通常为了提高可用性,分片也会引入多地容灾的方案用trade-off。单一数据多节点备份,通过"仲裁",提升数据库的可读可写能力。
适用案例
几个适用键值数据库的情况。
存放会话信息 用户配置信息 购物车数据
不适用场合
不是最佳方案
数据间关系(难在不同数据集之间建立关系) 含有多项操作的事务 查询数据(键值对的某部分值查询) 操作关键字集合(一次只能操作一个键)
8
文档数据库
此类数据库可存放并获取文档,其格式可以是 XML、JSON、BSON 等。
这些文档具备自述性,呈现分层的树状结构,可包含映射表、集合和纯量值。
什么是文档数据库
流行的文档数据库有:MongoDB、CouchDB、Terrastore、OrientDB 等等。
文档数据库的文档没有空属性,若其中不存在某属性,我们就假定该属性值未设定或与此文档无关。向文档中新增属性时,既无需预先定义,也不用修改已有文档内容。
特性(以 MongoDB 为例)
每个文档数据库都具备同类数据库没有的一些特性。
"单文档级别"的事务叫"原子事务",此类数据库的"事务"一般无法执行多个操作。在默认情况下,所有写入操作都将顺利执行。
可用性
文档数据库试图用主从式数据复制技术增强"可用性"。应用程序代码一般不需要检测主节点是否可用。MongoDB 通过"副本集"实现"复制",以提供较高的"可用性"。
一致性
副本集中至少有两个节点参与"异步主从复制"。所有请求都由主节点处理,而其数据会复制到从节点。若主节点故障,则"副本集"中剩下的节点会在其范围内选出主节点,所有后续请求就交由新的主节点处理,从节点也开始从新的主节点处获取数据。当原来的主节点从故障中恢复时,它会作为从节点重新加入,并获取全部最新数据。
文档数据库有个好处:它可以查询文档中的数据,而不用像键值数据库一样,必须根据关键字获取整个文档,然后再检视内容。这个特性使得此类数据库查询功能更接近于关系型数据库的查询模型。
可扩展性
扩展通过创建新节点同步;或者进行"分片"操作,根据特定字段划分数据,需要保证数据平衡散步在各片区内,以获得较好的写入效率,同时可以考虑把每个"分片"做成副本集,以提高读取效率。
适用场合
事件记录 内容管理系统及博客平台 网站分析与实时分析 电子商务应用程序
不适用场合
包含多项操作的复杂事物(不适合执行跨文档的原子操作) 查询持续变化的聚合结构(持续变化,意味着需要以最低级别的粒度保存聚合,实际上等于要统一数据格式。而文档数据库没有固定的数据格式)
9
列族数据库
本章以 Cassandra 为例,它是一种能快速执行跨集群写入操作并易于对此扩展的数据库,集群中没有主节点,其中每个节点均可处理读写请求。此外列族数据库还有 HBase、Hypertable、DynamoDB 等其他产品。
列族数据库将数据存储在列族中,而列族里的行则把许多列数据与本行的"行键"关联起来。列族用来把通常需要一并访问的相关数据分成组。
特性
Cassandra 基本存储单元叫"列",由一个"名值对"组成,其名也充当关键字。每个键值对都占据一列,并且均存有一个"时间戳"。若某列数据不再使用,则数据库可于稍后的"压缩阶段"回收其所占空间。
{
//行
name:"yiXiaoQi",
//列
value:"so cool",
//时间戳
timestamp:1617411836}
行是列的集合,列都附于某个关键字名下,或与之相连。由相似行所构成的集合就是列族。如果列族中的列都是"简单列"(simple column),则称其为"标准列族"。
与关系型数据库不同点在于:列族数据库的各行不一定要具备完全相同的列,并且可以随意向其中某行加入一列,而不用把它添加到其他行中。
//colum family
{
//row
"friday":{ "weather":"cloudy", "staus":"work", "lastVisit":"2021/4/2" }
//row
"satuday":{ "weather":"cloudy", "staus":"study", "isAlone":"yes" }}
"friday"与"satuday"这两行所具有的列不同,而它们都是列族的一部分。
如果某列中包含一个由小列组成的映射表,则称它为"超列",它有名和值。而值是一个由小列组成的映射表,可将其超列视为"列容器"。
{ name:"wechatNum:qq493658160" value:{ "author":"chenLiuSu", "staus":"study", "isAlone":"yes" }}
超列族适合将相关数据存在一起。对于那么大部分时间都用不到的列,Cassandra 仍然要将其反序列化,此时便不是最优方案。
Cassandra 将标准列族和超列族都放入"键空间"里,"键空间"与关系型数据库中的"数据库"类型,与应用程序有关的全部列族都存放于此。必须先创建列空间,才能为其增添列族:create keyspace ecommerce。
一致性
Cassandra 收到写入请求后,会先将代写数据记录到"提交日志"(commit log)中,然后将其写入内存里一个名为"内存表"(memtable)的结构中。
【是否觉得这样操作与 MySQL 数据库的 WAL 操作很像,有机会的话我会分享一期秋招时准备的 MySQL 笔记,希望多多关注。】
写入操作在写入"提交日志"及"内存表"后,即算成功了。写入请求成批堆积在内存中,并定期写入一种叫做"SSTable"的数据结构中。该结构中的缓存一旦写入数据库,就不会在向其继续写入了,则我们需新写一张 SSTable。
无用的 SSTable 可由"压缩"操作回收。Cassandra、HBase,LevelDB,RocksDB 这些 NoSQL 存储都是采用的 LSM 树(Log-Structured-Merge-Tree)。
LSM-tree 适用于索引插入比检索更频繁的应用系统。通过滚动合并和多页块的方法推迟和批量进行索引更新,充分利用内存来存储近期或常用数据以降低查找代价,利用硬盘来存储不常用数据以减少存储代价。
MemTable-内存 Immutable MemTable-中间态 SSTable(Sorted String Table)-磁盘
LSM 树的 compact 策略,两种基本:size-tiered 和 leveled。在此过程中就涉及到下述几个概念:
读放大:读取数据时实际读取的数据量大于真正的数据量。例如在 LSM 树中需要先 MemTable 查看当前 key 是否存在,不存在继续从 SSTable 中寻找 写放大:写入数据时实际写入的数据量大于真正的数据量。例如在 LSM 树中写入时可能触发 Compact 操作,导致实际写入的数据量远大于该 key 的数据量。 空间放大:数据实际占用的磁盘空间比数据的真正大小更多。上面提到的冗余存储,对于一个 key 来说,只有最新的那条记录是有效的,而之前的记录都是可以被清理回收的。
不小心扯的有点远,我们继续回来看看"一致性"设定是如何影响读取操作的。
Cassandra 一致性的级别有:ANY、ONE、TWO、THREE、QUORUM、LOCAL_QUORUM、EACH_QUORUM 以及 ALL,下面就稍微讲几个级别,看看它们是如何影响一致性的。
若将"一致性"设为 ONE,并将其作为所有读取操作的默认值,那么当 Cassandra 收到读取请求会返回第一个副本的数据。
若该数据中包含的时间戳过旧,则会启动"读取修复",使得后续读取操作能获得最新数据。假如业务对数据的读取效率有要求,或者数据内容比较稳定、不受时间影响,可以考虑采用低级别的一致性。
当以最低一致性执行写入操作,Cassandra 只会将单份数据写入一个节点的提交日志并向客户端返回响应。倘若该节点在同步数据到其他节点的过程中出现了故障,那就会导致数据丢失,引发业务风险。
在之前"分布式模型"一章中提到过"仲裁"的概念。Cassandra 还能将一致性设置为 QUORUM,在读取操作将过半数的节点响应后,根据时间戳返回最新的列,并通过"读取修复"操作把最新数据复制到其他副本中。
而"一致性"为 QUORUM 的写入操作则必须等所写数据传播至过半数节点后,才能结束写入操作。
如果将一致性级别设成 ALL,那么全部节点必须响应读取或写入操作,这样可用性会急剧下降:一旦某个节点"哑火",应用中的全部操作都将阻塞失败。
事务
Cassandra 没有传统意义上的"事务",也就是那种可以封装多个写入操作并决定是否提交数据变更的东西。
写入操作在"行"级别是"原子的"。写入操作可以是,根据特定行键向行内插入列数据或更新列数据。
写入操作首先会写在"提交日志"以及"内存表"中,只有它向两者写入数据成功才算执行完毕,可根据提交日志将数据恢复到此节点内,类似于日志重放操作。
可用性
可用性,可以参考"R+W~N"的关系,综合考虑设置,使得读写操作的一致性达到预期。
查询功能
在列族插入数据后,每行的数据都会按列名排序。假如某一列的读取次数比其他列更频繁,为了性能起见应该讲其值用作行键。
同时,Cassandra 支持类似 SQL 的查询语言 CQL(Cassandra Query Language)。
命令可以创建列族、查询与前面相同的数据、读取数据、创建索引等
可扩展性
集群扩展意味着要增加更多的节点。由于不存在主节点的设计,所以向集群中新增节点后,即可改善其服务能力,令其可以处理更多的读写操作,这种横向扩展可以尽可能提高正常运行时间。
适用案例
事件记录 内容管理系统与博客平台 计数器 限期适用(TTL 设计)
不适用案例
在开发早期原型不确定,不适合用 Cassandra,因为初期无法确定查询模式的变化,列族的设计无法确定,会降低开发效率。频繁改动的代价也会比关系型数据库修改数据模式的代价高。
10
图数据库
图数据库可存放实体(Node)及实体间的关系(edge)。边具有方向性以及可有多个属性,节点则按关系组织起来,以便在其中查找所需模式。
用图模型将数据一次性组织完毕,后续可根据"关系"以不同方式查询,查询图的方式称为"遍历",大家经常听到的 BFS/DFS,就是用来做图的快速检索。
节点间可由多种不同的关系类型,即能表现领域实体(domain entity)之间的关系,也可以表示辅助关系(secondary relationship)。正由于节点关系的数量及类型不限,所以这些关系可存放在同一图数据库中。
特性
本章以 Neo4J 为代表,讨论图数据库的工作原理。
方向性有助于设计出丰富的领域模型,可根据传入关系与传出关系双向遍历节点。关系不只含有类型、起始节点与终止节点,还有自己的属性。使用这些属性,可令关系更加有价值。
一致性
图数据库通过事务来保证一致性。它们不允许出现"悬挂关系":所有关系必须具备起始节点与终止节点,而且在删除节点前,必须移除上面附加的关系属性。
查询功能
可以用"索引服务"来编订节点属性索引,也能索引关系、边的属性,以便根据属性值查询。开始遍历图之前,应先查询索引以找出起始节点。
可扩展性
图数据库分片比较难,因为它们不是面向聚合的,而是面向关系的。
由于任何节点都可能关联其他节点,所以把相关节点放在同一个服务器中,遍历图时会更方便。若图中的节点放在不同电脑上,则遍历性能不佳。
图数据库一般有三种扩展方式:
给服务器配置足量内存,使之可完全容纳"工作集"中的全部节点与关系。 增加仅能读取数据的从节点,所有写入操作仍由主节点负责。 可用"领域特定知识"在应用程序端对其分片。使用"应用程序级分别"时,需要明白节点存放在地理位置不同的数据库中。
适用案例
互联数据 基于位置的服务 推荐引擎
不适用案例
在更新全部或某子集内实体的情况就显得不太适合。只要一个属性变了,全部实体就得跟着更新。
尤其在执行"全局图操作"时,更是如此。