是的,我们可以!高性能分布式ACID事务
Karthik Ranganathan 【hudson译】
创始人兼首席技术官
2018年4月24日
ACID事务是开发面向用户的关键业务应用的基本构建块。它们简化了确保数据完整性的复杂任务,同时支持高度并发的操作。虽然它们在单体SQL/关系数据库中是理所当然的,但分布式NoSQL/非关系数据库中要么完全放弃,要么只支持高度受限的单行风格(的事务)(见下文)。通常放弃ACID的理由是提高性能(以低延迟和/或高吞吐量衡量)。
YugabyteDB是一个高性能分布式SQL数据库,支持任意规模的跨多行、多分片和多个节点完全分布式ACID事务。正如一位鼓舞人心的领导者曾经说过的,是的,我们可以! 。这篇文章揭示了当今分布式数据库中ACID事务的状态,并解释了YugabyteDB如何在不影响高性能的情况下使实现分布式ACID事务。
定义ACID事务
事务是作为单个逻辑工作单元执行的一系列操作。事务有四个关键属性: — 原子性、一致性、隔离性和持久性 — 通常缩写为ACID。
- 原子性指事务中的所有工作被视为一个原子单元 — 要么全部执行,要么全部不执行。
- 一致性确保数据库始终处于一致的内部状态。例如,对于具有二级索引的表,主表和所有索引表在更新后应保持一致。
- 隔离性确定一个事务所做的更改如何/何时对另一个事务可见。从严格的角度来看,可串行化和快照是最高的两个隔离级别。
- 持久性确保交易结果永久存储在系统中。即使在断电或系统故障的情况下,修改也必须持久化。
在分布式数据库的上下文中,ACID事务在内部可分为以下三种类型。
单行ACID
所有操作仅影响一行(也就是键)的事务称为单行ACID事务。由于在大多数分布式数据库中,单行的数据通常不会跨越单个节点的边界,因此在分布式数据库中更容易实现单行ACID事务。然而,大多数NoSQL数据库甚至不能支持这种类型的事务,因为它们最终一致的存储引擎对数据读取的正确性没有内在保证。值得注意的例外是MongoDB,它支持多文档分布式事务.
单分片ACID
与单行ACID相比,一个稍微改进的是单分片ACID,其中事务操作中涉及的所有行都位于分布式数据库的单个分片。由于单个分片总是位于单个节点内,因此这种风格也不涉及跨多个节点协调事务操作,因此更容易在分布式数据库中实现。例如:MongoDB宣布从4.0版本开有意图支持单分片事务。
分布式ACID
在自动分片的分布式数据库中,如YugabyteDB、Google Cloud Panner和Azure Cosmos DB,集群创建时,通过设计将分片分布在多个节点上。影响跨域多节点分片上的一组行的事务称为分布式ACID事务。在可伸缩的数据库中实现分布式ACID事务需要使用事务管理器来协调各种操作,然后根据需要提交/回滚事务。流行的NoSQL数据库在设计上就避免这种额外的复杂性,因为担心它们会影响性能(以增加写入延迟和降低线性可伸缩性的形式)。正如我们在下面所看到的,这种担心并非基于现实,通过一个具有分布式ACID事务和高性能的单一分布式数据库,确实可以提高应用程序开发人员的灵活性。 由苹果公司开源的FoundationDB遵循类似的设计理念,它通过使用事务授权来处理事务.
YugabyteDB中的分布式ACID事务
YugabyteDB的分片、复制和事务的设计灵感来自2012年发表的谷歌Spanner论文。其目标是在不影响正确性的情况下提供高性能的分布式事务。由于分布式事务不是应用程序需要的唯一类型的事务,YugabyteDB有效地检测和管理涉及单行/单分片事务(使用分片共识,无需任何两阶段提交)和分布式事务(具有两阶段提交)的不同场景。为简单起见,我们将研究分布式事务如何操作跨越多节点上的多个键(这样一种)一般情况。
以下步骤概述了YugabyteDB中事务的生命周期。
步骤1.选择事务管理器
YugabyteDB有一个内置的事务管理器来管理事务的生命周期。事务管理器在每个节点上运行(作为tablet 服务器进程的一部分)并在添加节点时自动扩展。
由于事务管理器是无状态的,因此进入的事务可以路由到任何节点。集群中的任何事务管理器都可以管理进入事务。为了优化事务的性能,Yugabyte查询层(YQL)尝试在拥有事务访问所需大部分数据的tablet服务器上调度事务。
步骤2.在交易状态表中创建一个条目
事务需要以可靠和容错的方式跟踪。这是必要的,因为事务可能尝试更新多个键,但在任何中间步骤都可能失败或中止。
为了跟踪事务,将在事务状态表中为事务创建一个新条目。下面是该事务条目存储的一部分信息。
- 交易ID是唯一标识交易的UUID。
- 状态,可以是挂起、已提交、 或中止之一。所有事务都以挂起状态开始,然后进入已提交或已中止状态,在该状态下,它们将永久保持,直到清除。
- 提交混合时间戳,这是事务的提交时间戳。这是用于多版本并发控制(MVCC)的最后一个时间戳,用于在事务提交时提供事务所做的各种更新。
- 参与tablet的ID列表,这是事务写入的最终tablet集。请在步骤#6中了解更多信息,该步骤描述了如何清理临时写入。
步骤3.写临时记录
即使在生命周期的这一时间点上,事务也无法预测任何更新是否会与另一个事务的更新冲突。因此,YugabyteDB将临时记录写入所有tablet,这些tablet和事务试图修改的键相关。这些记录是临时的,因为在事务提交之前,它们对查询是不可见的。
除了存储由事务更新的数据之外,临时记录还充当持久的可撤销锁。这些锁由临时记录表示,可以由另一个冲突事务撤销。 冲突解决子系统确保对于任何两个冲突事务,至少其中一个事务被中止。
此外,还存储了交易元数据记录,以便有效地查找给定交易的以下信息:
- 事务协调者
- 优先级,用于决定要中止哪些冲突事务
- 作为此tablet中事务的一部分写入的所有临时记录
这样做是为了提高下面步骤4b中讨论的事务提交步骤的性能。
步骤4a。处理冲突
当多个事务同时运行时,它们可能会尝试更新同一组键。发生这种情况时,除非检测并处理冲突,否则这些更新可能会违反正在运行的事务的隔离保证。冲突更新集取决于操作(读与写)和隔离级别保证(可串行化与快照隔离)。
下表列出了必须检测和解决的冲突的简单视图。解决方案通常是在冲突可以解决的稍后时间点,在内部重新启动一个冲突事务。
可串行化和快照隔离级别的冲突操作
YugabyteDB目前自动检测并处理快照隔离级别的写-写冲突。其他类型冲突操作将作为即将推出的可串行化隔离级别支持的一部分自动处理和解决。
步骤4b。提交事务
一旦事务管理器成功写入所有临时记录,随后它将通过向包含事务状态的tablet发送RPC请求来提交事务。只有当事务由于冲突尚未中止时,提交操作才会成功。提交操作本身的原子性和持久性由事务状态的tablet Raft组保证。提交操作完成后,所有临时记录立即对客户端可见。
在事务状态tablet上,将已事务提交条目附加到其Raft日志这一时刻的当前混合时间被选择为事务的提交时间戳 。然后,它被用作常规记录的最终MVCC时间戳,常规记录在应用和清理临时记录时替换事务的临时记录。
步骤5.将确认发送回客户端
一旦事务被提交,事务管理器就向客户端发送事务成功的确认。
步骤6.清理临时记录
事务状态tablet向参与事务的每个tablet发送清理请求。因为参与的tablet ID列表被存储为事务条目的一部分,该操作可以有效地完成。
清理请求包含一个带有事务ID和提交时间戳的特殊应用记录。收到该请求后,tablet删除属于该事务的临时记录,并使用正确的提交时间戳写入永久记录。
一旦所有参与的tablet成功处理了这些应用记录,事务状态tablet就可以删除事务状态记录。状态记录的删除是通过在其tablet的Raft日志中写入一个特殊的apply else条目来实现的。在该时间点后不久,作为旧Raft记录的常规垃圾收集的一部分,属于此事务的Raft日志条目将从tablet的Raft记录中清除。
现在,数据已准备就绪,如事务读中所述路径。
在分布式事务中实现高性能
为了在保持分布式ACID事务保证的同时实现高性能,YugabyteDB中使用了许多技术。下文概述了其中的一些。
1.积极缓存正在进行的事务
一个给定的正在进行的可能需要查找自己的元数据(如已更新的tablet)。其他事务也可能需要查找有关冲突事务的信息,并根据相对优先级中止它们。YugabyteDB积极缓存正在进行的事务的信息,以使查找非常快速和高效。
2.细粒度锁定
YugabyteDB支持细粒度锁定以执行冲突解决。这对于在文档orie中处理具有性能的分布式事务至关重要
2.细粒度锁定
YugabyteDB支持细粒度锁定以执行冲突解决。这对于在面向文档的数据库(YugabyteDB 核心)中以高性能处理分布式事务至关重要。如果没有细粒度锁,更新文档中不重叠属性的事务最终可能会相互竞争。
下图显示了YugabyteDB实现的细粒度锁的类型。
例如,如果事务修改表中行内的单个列,则可能采用以下细粒度锁:
- 在行(文档根)上使用弱锁以确保它不能被另一个事务同时删除,但允许更新列的事务并行继续。
- 对正在更新的列使用强锁来序列化对该列的更新,以确保一致性。
3.安全时间
YugabyteDB为每个读请求都分配了一个特定的混合时间(对于MVCC) — 读取混合时间戳。这允许对同一组键的写入操作与读取并行进行,确保了高性能。
然而,至关重要的是,数据库视图在这个读取混合时间戳时不会通过同时发生的写入进行更新。这样做是为了确保在重试读取操作时,在特定时间戳连续读取数据不会看到不同的结果。为了实现这一点,YugabyteDB使用了“混合时间领导者租约”的概念。YugabyteDB处理以下操作之间的复杂依赖关系,以确保正确性:
- 键的单行更新
- 在交易完成之前为一行写入的临时记录
- 从数据库读取行的值
4.自动检测和优化单行ACID和分布式ACID
YugabyteDB经过专门设计,能够在不影响正确性的情况下检测两个流。
- -优化单行ACID事务, 在没有冲突操作时具有低延迟。
- -如果存在冲突操作,或者查询正在尝试需要写入临时记录的分布式/多分片事务性操作,YugabyteDB会自动切换到保留正确性,即使这意味着更高的延迟。
因此,YugabyteDB允许使用单行ACID语义对写操作进行批处理,以在流式摄取上实现非常高的性能,同时允许对一组具有一致性的行进行事务性更新。
总结
在开发面向用户的web和移动应用程序时,事务是一个基本功能。通常,此类应用程序中的大部分工作负载需要具有单行ACID保证的高性能,而一小部分工作负载则需要具有极端数据完整性的分布式ACID事务。YugabyteDB的设计旨在在这两种情况下取得良好的平衡。最终目标是通过一个统一的数据平台,将SQL、互联网规模增长和全球分布结合起来,从根本上简化应用程序开发。我们继续努力的两个领域是在故障条件下验证数据正确性的Jepsen测试 ,以及其他分布式事务工作负载的性能基准测试。请继续关注结果。
如果我们的数据库设计方法听起来令人兴奋,请尝试YugabyteDB — 它是Apache 2.0开源!今天就开始安装,几分钟内就可查看分布式ACID事务如何在笔记本电脑上工作。此外,不要忘记用你的点赞、关注和创建分支支持我们的GitHub项目。
\