6.1 缩写之战:BASE vs. 酸
“在化学中,pH 测量水溶液的相对酸度。pH 范围从 0(强酸性物质)到 14(强碱性物质);25°C 的纯水的 pH 值为 7,呈中性。 数据工程师用这个比喻来比较数据库的交易可靠性。” 可能的想法是:pH 值越高,即 数据库越接近“碱性”(“BASE”),交易越不可靠。 |
流行的关系型数据库,如MySQL,就是在ACID的基础上出现的。但是在过去的十年里,所谓的 NoSQL 数据库,在这个名称下结合了几种非常不同类型的数据库,在没有 ACID 的情况下也做得很好。事实上,有大量使用 NoSQL 数据库的开发人员根本不关心事务及其可靠性。让我们看看他们是否正确。
你不能笼统地谈论 NoSQL 数据库,因为它只是一个很好的抽象。NoSQL 数据库在数据存储子系统的设计上各不相同,甚至在数据模型上也各不相同:NoSQL 既是面向文档的 CouchDB,又是面向图形的 Neo4J。但是如果我们在事务的上下文中谈论它们,它们往往在一件事上是相似的:它们提供有限版本的原子性和隔离性,因此不提供 ACID 保证。要理解这意味着什么,让我们回答这个问题:如果不提供 ACID,它们提供什么?没有什么?
并不真地。毕竟,它们和关系数据库一样,也需要用漂亮的包装来推销自己。他们想出了自己的“化学”缩写 - BASE。
6.2 BASE 作为对手
在这里,我不会按字母顺序排列,但我将从基本术语 - 一致性开始。我将不得不平衡你的识别效果,因为这种一致性与 ACID 的一致性关系不大。术语一致性的问题在于它在太多上下文中使用。但是这种一致性有更广泛的使用背景,实际上这正是讨论分布式系统时所讨论的一致性。
我们上面谈到的关系数据库提供不同级别的事务隔离,其中最严格的确保一个事务不能看到另一个事务所做的无效更改。如果你站在商店的收银台,这时租金已经从你的账户中提取,但是租金转账交易失败,你的账户又回到了之前的价值(这笔钱是not debited),那么你在结账时的支付交易不会让所有人注意到这些手势——毕竟那笔交易从来没有经过,基于交易隔离的要求,它的临时变化是无法被其他交易注意到的。
许多 NoSQL 数据库放弃了隔离保证并提供“最终一致性”,您最终会看到有效数据,但您的事务有可能读取无效值 - 即临时的,或部分更新的,或过时的。读取时数据可能会在“惰性”模式下变得一致(“lazily at read time”)。
NoSQL 被设想为用于实时分析的数据库,为了获得更快的速度,他们牺牲了一致性。Eric Brewer,也是创造 BASE 一词的人,制定了所谓的“CAP 定理”,根据该定理:
对于分布式计算的任何实现,可以提供不超过以下三个属性中的两个:
- 数据一致性(consistency)——不同节点(实例)上的数据不会相互矛盾;
- 可用性(availability)——对分布式系统的任何请求都以正确的响应结束,但不保证所有系统节点的响应都相同;
- 分区容忍度(partition tolerance)——即使节点之间没有连接,它们也继续彼此独立地工作。
如果您想对 CAP 有一个非常简单的解释,那么这里就是了。
有意见认为 CAP 定理不起作用,一般表述过于抽象。无论如何,NoSQL 数据库通常在 CAP 定理的上下文中拒绝一致性,它描述了以下情况:数据已在具有多个实例的集群中更新,但更改尚未在所有实例上同步。请记住,我在上面提到了 DynamoDB 示例,它告诉我:您的更改变得持久 - 这是给您的 HTTP 200 - 但我只在 10 秒后看到更改?开发人员日常生活中的另一个例子是 DNS,即域名系统。如果有人不知道,那么这正是将http(s)地址翻译成IP地址的“字典”。
更新的 DNS 记录根据缓存间隔设置传播到服务器 - 因此更新不会立即引起注意。好吧,类似的时间不一致(即最终一致性)可能发生在关系数据库集群(比如 MySQL)上——毕竟,这种一致性与 ACID 的一致性无关。因此,重要的是要理解,从这个意义上说,当涉及到集群中的多个实例时,SQL 和 NoSQL 数据库不太可能有很大不同。
此外,端到端的一致性可能意味着写入请求会乱序:即所有数据都会被写入,但最终接收到的值不会是写入队列中的最后一个。。
由于端到端的一致性模型,非 ACID NoSQL 数据库具有所谓的“软状态”,这意味着即使没有输入,系统状态也会随时间变化。但是这样的系统努力提供更大的可访问性。提供 100% 的可用性并不是一项微不足道的任务,因此我们谈论的是“基本可用性”。这三个概念:“基本可用”、“软状态”(“软状态”)和“最终一致性”构成了首字母缩写词 BASE。
老实说,在我看来,BASE 的概念比 ACID 更像是一个空洞的营销包装——因为它没有提供任何新内容,也没有以任何方式表征数据库。而给某些数据库附加标签(ACID、BASE、CAP)只会让开发人员感到困惑。我决定无论如何都要向你介绍这个术语,因为在研究数据库时很难绕过它,但既然你知道它是什么,我希望你尽快忘记它。让我们回到隔离的概念。
6.3 所以 BASE 数据库根本不符合 ACID 标准?
本质上,ACID 数据库与非 ACID 的不同之处在于非 ACID 实际上放弃了隔离。理解这一点很重要。但更重要的是阅读数据库文档并像 Hermitage 项目的人那样测试它们。这个或那个数据库的创建者如何称呼他们的创意并不重要——ACID 或 BASE,CAP 或非 CAP。重要的是这个或那个数据库到底提供了什么。
如果数据库的创建者声称它提供 ACID 保证,那么这可能是有原因的,但建议您自己进行测试以了解是否如此以及在何种程度上如此。如果他们声明他们的数据库不提供此类保证,那么这可能意味着以下内容:
-
数据库不提供原子性保证。虽然一些 NoSQL 数据库为原子操作提供了单独的 API(例如 DynamoDB);
- 数据库不提供隔离保证。这可能意味着,例如,数据库不会按照写入数据的顺序写入数据。
至于持久性保证,很多数据库为了性能都在这一点上做出了妥协。写入磁盘是一个太长的操作,有几种方法可以解决这个问题。我不想深入探讨数据库理论,但为了让您大致了解应该如何看待,我将概括地描述不同的数据库如何解决持久性问题。
要比较不同的数据库,除其他事项外,您需要了解特定数据库的数据存储和检索子系统下的数据结构是什么。简而言之:不同的数据库有不同的索引实现——即组织对数据的访问。其中一些可以让您更快地写入数据,而另一些则可以让您更快地读取数据。但是也不能笼统的说有的数据结构让耐久性变高或者变低。
6.4 不同的数据库如何索引数据,以及这如何影响持久性等
存储和检索数据有两种主要方法。
最简单的保存数据的方式就是以类日志的方式在文件的末尾添加操作(即总是会发生追加操作):不管我们是想添加、更改还是删除数据——都CRUD 操作只是写入日志。搜索日志效率低下,这就是索引的用武之地 - 一种特殊的数据结构,用于存储有关数据存储位置的元数据。最简单的日志索引策略是跟踪键和值的哈希映射。这些值将引用写入文件内部的数据的字节偏移量,这就是日志(log)并存储在磁盘上。这种数据结构完全存储在内存中,而数据本身在磁盘上,称为 LSM 树(日志结构合并)。
您可能想知道:如果我们一直将我们的操作写入日志,那么它会增长过快吗?是的,因此发明了压缩技术,它以某种周期性“清理”数据,即只为每个键留下最相关的值,或删除它。而如果我们在磁盘上有多个日志,而是多个,并且它们都是排序的,那么我们将得到一个名为 SSTable(“排序字符串表”)的新数据结构,这无疑会提高我们的性能。如果我们想在内存中排序,我们会得到一个类似的结构——所谓的 MemTable,但随之而来的问题是,如果发生致命的数据库崩溃,那么最后写入的数据(位于 MemTable 中,但尚未写入到磁盘)都丢失了。实际上,
另一种索引方法是基于 B 树(“B 树”)。在 B 树中,数据以固定大小的页面写入磁盘。这些数据块的大小通常约为 4 KB,并且具有按键排序的键值对。一个 B 树节点就像一个包含一系列页面链接的数组。最大限度。数组中的链接数称为分支因子。每个页面范围都是另一个 B 树节点,带有指向其他页面范围的链接。
最终,在工作表级别,您会找到单独的页面。这个想法类似于低级编程语言中的指针,只是这些页面引用存储在磁盘上而不是内存中。当数据库中发生 INSERT 和 DELETE 时,某个节点可以分裂成两个子树以匹配分支因子。如果数据库在过程中由于任何原因出现故障,则数据的完整性可能会受到损害。为了防止这种情况发生,使用 B 树的数据库维护了一个“预写日志”或 WAL,其中记录了每个事务。此 WAL 用于在 B 树损坏时恢复其状态。似乎这就是使使用 B 树的数据库在持久性方面更好的原因。但是基于 LSM 的数据库也可以维护一个文件,该文件本质上执行与 WAL 相同的功能。因此,我将重复我已经说过的话,也许不止一次:了解您选择的数据库的运行机制。
B 树可以肯定的是它们有利于事务性:每个键只出现在索引中的一个位置,而日志存储子系统可以在不同的段中有相同键的多个副本(例如,直到执行下一次压缩)。
但是,索引的设计直接影响到数据库的性能。对于 LSM 树,写入磁盘是顺序的,而 B 树会导致多次随机磁盘访问,因此 LSM 的写操作比 B 树更快。这种差异对于磁性硬盘驱动器 (HDD) 尤其重要,其中顺序写入比随机写入快得多。LSM 树上的读取速度较慢,因为您必须查看处于不同压缩阶段的多个不同数据结构和 SS 表。更详细地说,它看起来像这样。如果我们使用 LSM 进行简单的数据库查询,我们将首先在 MemTable 中查找键。如果不存在,我们查看最近的 SSTable;如果不存在,那么我们查看倒数第二个 SSTable,依此类推。如果请求的密钥不存在,那么使用 LSM 我们将最后知道这一点。LSM 树用于,例如:LevelDB、RocksDB、Cassandra 和 HBase。
我描述得如此详细,以便您了解在选择数据库时,您需要考虑许多不同的事情:例如,您希望写入数据还是读取数据更多。而且我还没有提到数据模型的差异(你是否需要遍历数据,就像图形模型允许的那样?你的数据中不同单元之间是否存在任何关系——那么关系数据库会来拯救?),和 2 种类型的数据模式——写入时(如在许多 NoSQL 中)和读取时(如在关系中)。
如果我们回到持久性方面,那么结论会是这样的:任何一个写入磁盘的数据库,不管索引机制如何,都可以为你的数据的持久性提供很好的保证,但是你需要针对每个具体的数据库进行处理,它到底提供了什么。
6.5 内存数据库如何工作
顺便说一下,除了写入磁盘的数据库之外,还有主要使用 RAM 的所谓“内存中”数据库。简而言之,内存数据库通常为了更快的写入和读取速度而提供较低的持久性,但这可能适用于某些应用程序。
事实上,RAM 内存长期以来一直比磁盘更昂贵,但最近它开始迅速变得更便宜,这催生了一种新型数据库——考虑到从 RAM 读取和写入数据的速度,这是合乎逻辑的。但你会问:这些数据库的数据安全性如何?再次在这里,您需要查看实现的细节。通常,此类数据库的开发人员提供以下机制:
- 您可以使用电池供电的 RAM;
- 可以将更改日志写入磁盘(类似于上面提到的 WAL),但不能写入数据本身;
- 您可以定期将数据库状态的副本写入磁盘(在不使用其他选项的情况下,不能提供保证,但只会提高耐用性);
- 您可以将 RAM 的状态复制到其他机器。
例如,主要用作消息队列或缓存的内存中 Redis 数据库缺乏 ACID 的持久性:它不能保证成功执行的命令将存储在磁盘上,因为 Redis 会将数据刷新到磁盘(如果您启用持久性)仅以定期异步方式进行。
然而,这对所有应用程序来说并不重要:我发现了一个 EtherPad 合作在线编辑器的示例,它每 1-2 秒刷新一次,用户可能会丢失几个字母或一个单词,这并不重要。否则,由于内存数据库的优点在于它们提供了难以用磁盘索引实现的数据模型,因此可以使用 Redis 来实现事务——它的优先级队列允许你这样做。
GO TO FULL VERSION