高可用 NoSQL 数据库是指服务无中断地持续运行的系统。许多基于网站的业务要求数据服务能够一直不中断。例如,在线购物的数据库需要保证 7 x 24 的可用性。
为什么需要它们一直运行?假设你的数据库支撑着一个全球化的电子商务网站,那么数分钟的宕机就可能造成一个消费者的购物车被清空,或是系统在德国主要消费时段停止响应。这些类型的故障将会使你的顾客转而选择你的竞争对手并降低你的消费者信任度。
分布式 NoSQL 系统降低了那些需要可扩展性和永久在线特性的系统的每笔交易成本。虽然多数 NoSQL 系统使用非标准的查询语言,但它们的设计和可以部署在低成本的云平台的能力,为那些因初创且需要提供永久在线功能的公司提供了可能的选择。
描述系统整体可用性最常用的方法是用“9”来描述可用性,其中的“9”是指在设计上的可用性概率中出现“9”的次数。所以“3 个 9”意味着一个系统被预测可以在 99.9% 的情况下可用,而“5 个 9”意味着那个系统应该有 99.999% 的可能是可用的。
下表展示了一个基于典型可用性目标计算出的每年宕机时间的例子。
量化整体系统可用性并不仅仅是计算出某个数字。为了客观地评估 NoSQL 系统,还需要了解系统可用性中的细化指标。
如果一个业务部门声明他们不能承担一个日历年宕机 8 小时的后果,那么就需要构建一个提供 3 个 9 可用性的基础设施系统。多数固定电话交换机的设计目标是达到 5 个 9 的可用性,或每年不超过 5 分钟的宕机时间。现今,除了某些需要更高可用性的场景,5 个 9 被认为是数据服务的黄金标准。
业界使用服务级别协议(service level agreement,SLA)这个术语来描述任何数据服务期望达到的可用性指标。SLA 是服务提供商和客户之间达成的一种书面协议。它定义了服务商需要提供的服务及其期望的可用性和响应时间,而非服务的提供方式。在起草 SLA 时需要考虑以下因素。
NoSQL 系统的可用性配置也许会和上面这些普适规则有出入,但关注点都不应该只是某个单一可用性指标。
现在,让我们来看看亚马逊为 S3 存储服务编写的 SLA。亚马逊的 S3 是现今最可靠的基于云端的 KV 存储服务,且即使在遇到大量读写高峰的情况下也能持续良好运行。传闻中,这个系统中存储的数据在 2012 年夏季达到了 1 万亿条,为目前容量最大的云端存储。这些数据平均下来大概能达到全球人均 150 条记录。
亚马逊在网站上声明了如下数个可用性指标。
仔细阅读亚马逊的 SLA 对你仍会有帮助。例如,协议定义错误率为 S3 返回了内部错误代码的请求个数,但完全没有提到任何与缓慢的响应时间相关的条目。
在实践中,多数用户将获得的可用性远超过 SLA 中写明的最小值。一个独立的测试服务发现 S3 能够达到 100% 的可用性,即使在长时间高负载的情况下也一样。
如果要构建一个 NoSQL 数据库,就要能够预测这个数据库的可靠性。你也需要一些工具帮助你分析数据库服务的响应时间。
可用性的预测方法是通过观测每个被依赖的(单点故障)系统组件的可用性估计值来计算系统总体可用性。如果每个子系统使用一个像 99.9 这样的简单可用性估计值,那么将每个数值相乘就可以得出系统总体可用性的估计值。
例如,如果有 3 个会造成单点故障的情况——网络有 99.9% 可用性、主节点有 99% 可用性、电源有 99.9% 可用性,那么总的系统可用性就是这 3 个数值的乘积:98.8%(99.9×99×99.9)。
如果有像主节点或名字节点这样的单点故障节点,那么 NoSQL 系统可以平滑地切换到备用节点而不会造成主要服务中断。如果一个系统可以快速地从一个失效组件的情况下恢复过来,这就是说该系统有自动故障转移(automatic failover)的特性。
自动故障转移是指系统能够监测到服务失效并自动切换到备用组件的特性。故障恢复是指恢复系统中失效组件到正常状态的操作过程。一般情况下,这个过程需要执行数据同步操作。如果系统只配置了一个备用节点,必须综合故障转移失败的概率和故障恢复前系统再次故障的可能性这两项数据来计算可用性。
除了故障指标,还有一些其他指标可用来评估可用性。如果系统有客户端请求响应大于 30s 即超时的配置,那么可以计算客户端请求失败的总占比。在这种情况下,被称为客户端收益(client yield)的量化指标可能是一个更好的参考因素。其中客户端收益是指任意请求在指定时间间隔内返回响应的可能性。
其他指标,比如收获指数(harvest metric),可以在引入部分 API 结果时纳入参考范围,类似联合搜索引擎这样的服务就可能返回部分结果。例如,搜索 10 个远程系统,如果有一个系统在等待结果的 30s 时间窗口内发生了故障,那么这次请求的收获指数就是 90%。收获指数可以通过可用数据除以总的数据源数得到。
要找到最适合应用需求的 NoSQL 服务也许需要比较两个不同系统的架构,而系统的真正架构可能隐藏在网络服务接口之后。在这种情况下,构建一个原型项目并模拟真实负载测试服务也许更有意义。
部署好一个包含压力测试的原型项目后,需要测量的一个关键指标是读写响应时间的频率分布图。这些分布图可为决定是否扩展数据库提供参考。这个分析中的一个关键点是应该关注响应中最缓慢的 5% 部分花了多长时间完成响应,而不是平均响应时间。一般来说,拥有稳定响应时间的服务的可用性比有时出现较高比例缓慢响应的系统要高。让我们来看看这种情况的一个示例。
假如小孙要为一个关心网页响应时间的业务部门评估两个候选 NoSQL 系统。网页由某个 KV 存储中的数据渲染。小孙已经将候选项缩小到了两个 KV 存储,我们将其叫作服务 A 和服务 B。如下图所示,小孙通过 JMeter(一种常用的性能监控工具)生成了两者响应时间的分布图。
平均情况和 95% 的情况下响应时间频率分布的对比图。需要注意的是,两条分布曲线分别对应的是两个 NoSQL KV 数据存储在负载下的表现。服务 A 拥有较低的平均响应时间,但在 95% 的情况下比 B 更慢。服务 B 则是拥有较高的平均响应时间,但 95% 的情况下比 A 更快
当小孙观测数据时,她发现服务 A 拥有较低的平均响应时间。但在 95% 的情况下,服务 A 响应时间比服务 B 高。服务 B 的平均响应时间可能较高,但仍在网页响应时间的期望范围内。在和该业务部门就测试结果讨论完后,该团队选择了服务 B,因为他们感觉在实际负载情况下服务 B 会有更稳定的响应时间。
现在,我们已经讨论过了预测和量化系统可用性的方法。接下来将探讨 NoSQL 集群用来提升系统可用性的策略。
你最初想要问的几个问题之一可能是:“如果 NoSQL 数据库崩溃了怎么办?”
为了解决这个问题,可以创建一个数据库复制。
对高可用性有需求的网站会使用一个叫负载均衡器(load balancer)的前端服务。下图展示了一个负载均衡器的示意图。
图中,服务请求从左边进入系统,然后被发送到一个被称为负载均衡池(load balancer pool)的资源池中。接着,被发送给负载均衡器主节点的服务请求会再被转发给某个应用服务器。理想情况下,每个应用服务器有某种负载状况指示来告诉负载均衡器它们的繁忙状况。最空闲的应用服务器将会接收到负载均衡器转发的请求。应用服务器响应请求服务并返回结果。应用服务器也可能向一个或多个 NoSQL 服务器发送数据请求。当查询请求的结果返回后,服务也就最终完结。
负载均衡器适用于有大量可以独立完成服务请求的节点的场景。为了获得性能提升优势,所有的服务请求首先到达负载均衡器,再由其分发给最空闲的节点。每个应用服务器发送的心跳信号构成了一个正在工作的应用服务器列表。一个应用服务器也可能向一个或多个 NoSQL 数据库发送数据请求。
多数 NoSQL 系统的设计目标之一就是能够和像 Hadoop 分布式文件系统(HDFS)这样的高可用文件系统协同工作。如果你正在使用像 Cassandra 这样的 NoSQL 系统,你将了解到它拥有一个和 HDFS 兼容的文件系统。基于某个特定文件系统来构建 NoSQL 系统既有好处也有不足。
将 NoSQL 数据库与分布式文件系统结合有以下优势。
将 NoSQL 数据库与分布式文件系统结合还有以下缺点。
HDFS 通常能够可靠地存储 GB 级到 TB 级的海量文件,同时,HDFS 还可以调整复制配置以支持单文件级别的复制策略。默认情况下,HDFS 中的多数文件都拥有 3 个复制副本。这意味着组成这些文件的数据块将备份存储在 3 个不同的节点上。一个简单的 HDFS shell 命令就可以更改任何 HDFS 文件或文件夹的备份数。有两个原因可能需要提高 HDFS 中文件的复制因子数配置。
降低备份数的主要原因一般是磁盘空间将被耗尽或是不再要求需要高复制数的服务级别。如果担心磁盘空间将被耗尽,那么在数据不可访问造成的损失较低且读取需求不严格的情况下,可以减小复制因子。另一方面,让复制数随着数据存储日期的变长而减小也比较常见。例如,超过 2 年的数据可能只有 2 个备份,而超过 5 年的数据就只有 1 个备份。
HDFS 提供的较好特性之一就是机架感知。机架感知功能可以根据节点在物理机架上的放置方式以及在一个机架内部网络中的连接方式对 HDFS 节点进行逻辑分组。位于同一个物理机架的节点之间通常拥有更高的带宽连接,而且使用这种网络能够将数据和其他共享网络进行隔离。下图展示了这种结构。
HDFS 通常能够可靠地存储 GB 级到 TB 级的海量文件,同时,HDFS 还可以调整复制配置以支持单文件级别的复制策略。默认情况下,HDFS 中的多数文件都拥有 3 个复制副本。这意味着组成这些文件的数据块将备份存储在 3 个不同的节点上。一个简单的 HDFS shell 命令就可以更改任何 HDFS 文件或文件夹的备份数。有两个原因可能需要提高 HDFS 中文件的复制因子数配置。
降低备份数的主要原因一般是磁盘空间将被耗尽或是不再要求需要高复制数的服务级别。如果担心磁盘空间将被耗尽,那么在数据不可访问造成的损失较低且读取需求不严格的情况下,可以减小复制因子。另一方面,让复制数随着数据存储日期的变长而减小也比较常见。例如,超过 2 年的数据可能只有 2 个备份,而超过 5 年的数据就只有 1 个备份。
HDFS 提供的较好特性之一就是机架感知。机架感知功能可以根据节点在物理机架上的放置方式以及在一个机架内部网络中的连接方式对 HDFS 节点进行逻辑分组。位于同一个物理机架的节点之间通常拥有更高的带宽连接,而且使用这种网络能够将数据和其他共享网络进行隔离。下图展示了这种结构。
亚马逊的 DynamoDB 论文是 NoSQL 运动中最具影响力的论文之一。这篇论文详细地介绍了亚马逊抛弃关系型数据库管理系统设计转而使用自己定制的分布式计算系统来满足他们的网页购物车对横向扩展和高可用性需求的细节。
最初,亚马逊并没有开源 DynamoDB。然而,尽管缺少源代码,这篇 DynomaDB 论文还是对诸如 Cassandra、Redis 和 Riak 等其他 NoSQL 系统产生了深远影响。在 2012 年 2 月,亚马逊将 DynamoDB 开放为数据库服务供其他开发者使用。这个案例研究将回顾亚马逊的 DynamoDB 服务以及将它作为全托管、高可用、可扩展的数据库服务的方法。
让我们从介绍 DynamoDB 的高层特性开始。Dynamo 的关键革新点是它能够快速且精确地调整吞吐量。这项服务能够可靠地承载大量的读写事务,并且可以通过调整网页上的配置完成分钟级别的性能调整。下图展示了它的用户界面的一个示例。
亚马逊的 DynamoDB 数据表吞吐量预设。通过修改读容量单位或写容量单位,可以将数据库中的每张表调整到满足容量需求的合适值。亚马逊还提供了计算应用容量需求的工具
DynamoDB 管理着节点的使用数量和节点间负载均衡的策略。亚马逊也提供了 API 接口可以根据负载监控系统的结果进行编程调整预见到的吞吐量。整个过程均不需要运维人员的介入。而且每月的亚马逊账单会根据这些参数修改进行调整。
为了开发 DynamoDB 服务,亚马逊使用了许多复杂的算法在数万台服务器中实现均衡可靠地分发读写事务。另外,DynamoDB 的独特之处还在于它是完全部署在固态硬盘(SSD)上的最初几个系统之一,而使用 SSD 让 DynamoDB 有了一个可预测的服务级别。
DynamoDB 的目标之一是提供稳定在几毫秒延迟内的读取响应。全部使用 SSD 意味着 DynamoDB 根本不需要考虑磁盘读取延迟。最终结果就是用户的所有 GET 和 PUT 操作均能够得到稳定的响应,而使用基于 DynamoDB 中数据进行渲染的网页也会比其他基于磁盘数据库中数据的网页快。
DynamoDB 的 API 提供细粒度的读取一致性控制。开发人员可以选择从本地节点读取一个中间结果(称为最终一致性)的方式,或是较慢但确保一致性(guaranteed consisten)的数据读取方式。这种确保一致性的读取会花费稍长的时间来确保读取数据的节点保存有最新的数据副本。如果应用知道请求的数据没有改动,那么直接读取会比较快。
这种读取数据的细粒度控制是利用对一致性需求的理解来调整应用的绝好例子。需要重点强调的是,总是可以强制要求读取结果一致,但对基于 SQL 的数据服务来说,这一需求会是个挑战,因为在分布式系统上执行的 SQL 并没有“在查找之前保持数据一致”的功能。
DynamoDB 非常适合于有弹性需求的组织。只为用到的服务付费的策略是节省服务器管理开销的主要方法。然而,当确实需要扩展时,DynamoDB 也提供满足业务快速增长需求的扩展空间。DynamoDB 还提供了可扩展的转化弹性 MapReduce 支持。这就意味着在需要执行可扩展的提取、转化、装载(ETL)过程时,可以快速地将海量数据移进移出 DynamoDB。
到现在为止,我们已经介绍了 NoSQL 系统用来实现高可用性的多种策略。接下来,让我们了解两个有着高可用性方面良好口碑的 NoSQL 产品。
这个案例研究将介绍 Apache Cassandra 数据库。它是一个在可扩展性和高可用性两方面均有良好口碑的 NoSQL 列族存储。即使在写入高负载的情况下,它也能为用户保证数据服务的高可用性。Cassandra 是纯对等分布式模型的早期实现者。它的集群中的所有节点均具有完全一致的功能,且客户端可以在任意时间点向任意一个节点写入数据。因为 Cassandra 集群中不存在任何单独的主节点,所以它的集群也就没有所谓的单点故障,因此也就不需要再去部署测试额外的故障转移节点。Apache Cassandra 本身是一个 NoSQL 技术的有趣结合体,所以有时也将它称为受 Dynamo 灵感启发的 BigTable 实现。
除了它健壮的对等模型,Cassandra 还将大量心思放在了集群的易部署性和读写一致性级别的易配置性上。下表展示了完成复制级别配置后,它所能提供的多种写入一致性级别设置。
用于指定 Cassandra 表写入一致性的代码。Cassandra 中的每张表在创建时就需要设置好满足一致性级别需求的配置。而且还能随时修改这些配置,Cassandra 也会根据修改自动地更新配置。读取一致性方面也有类似的配置。
接下来,需要考虑的是一个读取事务中某个节点变得不可用时的策略。怎样指定返回新数据前需要检查的节点数?只检查一个节点可以使请求快速返回,但得到的数据可能已经过期。而检查多个节点可能会多花费数毫秒,但能确保获取到数据的最新版本。最好的方法是让客户端指定和前面介绍的写入一致性代码类似的读取一致性代码。在读取数据时,Cassandra 客户端可以根据需求从 ONE、TWO、THREE、QUORUM、LOCAL_QUORUM、EACH_QUORUM 和 ALL 中选择合适的代码。甚至可以使用 EACH_QUORUM 在返回数据前检查位于世界各地的多个数据中心。
接下来,将介绍部署配置 Cassandra 集群之前需要理解的具体配置项。
在关于一致性散列的讨论中,我们介绍了在集群中使用散列来均匀分发数据的技术。Cassandra 使用了相同的技术来完成数据的均匀分发。在深入理解 Cassandra 的实现方式之前,让我们先介绍一些 Cassandra 中的关键术语和定义。
1) 行键
行键(rowkey)是一个数据行的标识符。Cassandra 会对这个值进行散列并根据得到的散列值将数据存放到一个或多个节点上。行键是用来决定数据存放节点的唯一数据结构,这个过程将不会用到任何列数据。设计好行键结构是保证相似数据聚合在一起以提供高速读取的关键步骤。
2) 分区
分区(partitioner)是根据键指定一行数据存放节点的策略。默认策略是随机选择一个节点。Cassandra 使用键的 MD5 散列值作为数据行的一致性散列值,这样就使得数据能够随机但均匀地分发到所有节点上。另一个选择则是使用行键中实际的字节(而非行键的散列值)来决定数据存放的节点。
3) 键空间
键空间(keyspace)是决定一个键如何在节点上复制的数据结构。默认情况下,可能会将需要更高级别可用性的数据的复制数设置为 3。
Cassandra 键空间通常可以看作一个环的形式,如下图所示。
使用 SimpleStrategy 配置复制 Cassandra 键空间的示例。数据项 A1、B1、C1 和 D1 被写入了一个复制因子设为 3、由 4 个节点组成的集群里。每个数据项都被写到 3 个不同节点上。在某个数据项完成第一个节点写入后,Cassandra 将按顺时针方向顺序寻找 2 个额外的节点继续写入该数据项。
Cassandra 允许根据键空间的属性对复制策略进行微调。在向 Cassandra 系统中添加任意一行数据时,必须将这行数据和键空间关联起来。每个键空间都允许配置修改该行的复制因子。下图展示了一个键空间定义的示例。
Cassandra 配置复制策略的示例。复制策略是键的一个属性,指明了所在网络类型和对应键复制数。
使用这个策略能够均匀地将数据行分发到集群的所有节点上,从而消除系统瓶颈。虽然可以使用键中特定的几位来关联键空间,但我们强烈建议不使用这种方式。因为它可能导致集群中出现热点节点并使集群管理复杂化。而这种方式最主要的局限是如果更改了分区算法,就不得不重新保存并恢复整个数据集。
当拥有多个机架或多个数据中心时,也许需要更改这个算法来保证在返回写入确认消息前,数据已经写入了多个机架甚至是多个数据中心中。如果将分发策略从 SimpleStrategy 改为 NetworkTopologyStrategy,Cassandra 将会遍历键空间环直到找到位于不同机架或数据中心的节点。
因为 Cassandra 拥有一个完整的对等部署模型,所以它看起来很适合那些期望使用同时具有高可用性和可扩展性的列族系统的组织。下一个案例研究将探讨 Couchbase 2.0 使用 JSON 文档存储实现对等模型的方法。
Couchbase 2.0 是一个使用了和其他 NoSQL 系统相同复制模式的 JSON 文档数据库。
Couchbase 与 CouchDB
Couchbase 技术不应该和 Apache CouchDB 混为一谈。虽然两者都是开源技术,但它们是两个独立的、有着不同特性以及用来支持不同应用开发和场景的开源项目。在底层结构上,Couchbase 和最初的 Memcached 项目的共同点比与最初的 CouchDB 项目的共同点更多。虽然 Couchbase 和 CouchDB 使用同样的生成 JSON 文档的算法,但具体的实现方式却不相同。
与 Cassandra 类似,Couchbase 也使用所有节点提供相同服务的对等分布式模型,以消除单点故障出现的可能。但与 Cassandra 不同的是,Couchbase 采用的是可以根据文档内容查询的文档存储而非列族存储。另外,Couchbase 也使用了键空间这一概念将键范围和每个节点关联起来。
下图展示了部署在多个数据中心的 Couchbase 服务器中的组件。Couchbase 将文档集合存储在被称为桶(bucket)的容器中。这些桶的配置管理和文件系统中的文件夹非常类似。桶的类型包括两种:缓存在内存中的桶(存储在内存中且会被清除)和存储在磁盘上并配置了复制的 Couchbase 桶。一个 Couchbase JSON 文档会被写入一个或多个磁盘上。针对高可用性系统的讨论,我们将主要关注 Couchbase 桶。
Couchbase 中高可用的文档。Couchbase 桶是配置用来实现高可用性的文档逻辑集合。Couchbase 客户端通过集群映射配置找到存储在当前活动节点上的文档(第 1 步)。如果数据服务器 1 不可用,集群映射配置将使存储在数据服务器 2 上的 doc1 的备份成为当前生效的版本(第 2 步)。如果美国西部数据中心宕机,客户端将会使用跨数据中心备份(XDCR)并使存储在位于美国东部地区的数据服务器 3 上的副本成为生效版本(第 3 步)
在内部,Couchbase 使用了一个被称为 vBucket(虚拟桶)的概念。这个概念关联了一个基于散列值切分出来的键空间的某个或某几个部分。Couchbase 的键空间和 Cassandra 中的键空间类似。但在数据存储时,它的键空间管理对外部是透明的。
需要注意的是,一个虚拟桶并不只是包含某个键空间范围,而是可能会包含许多非连续的键空间。值得庆幸的是,用户并不需要考虑键空间的管理或是虚拟桶的工作原理。Couchbase 客户端仅仅是和这些桶进行交互,而让 Couchbase 服务器去考虑应该从哪个节点上找到存储在某个桶中的数据。将桶和虚拟桶区分开来是 Couchbase 实现横向扩展的主要方式。
通过使用集群映射表中的信息,Couchbase 会在主节点和备份节点上各存储一份数据。如果 Couchbase 集群中的任何节点失效,该节点就会被打上一个故障转移的标记,而集群随之会根据这标记更新集群映射表。所有指向该节点的数据请求都会被转向到备份节点。
在某个节点失效与对应备份节点接管后,用户通常会开始一个重平衡操作——向集群中添加新节点以使集群恢复之前的容量。重平衡操作将更新虚拟桶和节点间的映射信息并使之生效。在重平衡期间,虚拟节点会被均匀地在节点间进行重分发以此最小化数据在集群中的移动。一旦某个虚拟桶在新节点上被重新创建,集群会自动禁用之前节点上的虚拟桶并启用新节点上的虚拟桶。
Couchbase 提供了使 Couchbase 集群在整个数据中心宕机的情况下也能不间断运行的功能。针对横跨了多个数据中心的系统来说,Couchbase 提供了一个被称为跨数据中心复制(cross data center replication,XDCR)的功能。这个功能使数据可以自动地备份到远程数据中心并在两个中心里均保持可用。如果一个数据中心发生宕机,另一个数据中心可以负责承担其负载并持续对外提供服务。
Couchbase 的最强特性之一是其内建的高精度监控工具。下图展示了其监控工具的一个示例。