eBay技术人员最近展示了一种称为GRIT的分布式事务协议,用于跨多个具有多个基础数据库的微服务进行ACID(原子性,一致性,隔离性,持久性)事务。
本文介绍了GRIT协议的基本思想,该思想在IEEE国际数据工程国际会议(ICDE)2019上宣布,并提供了使用该协议的一部分为JanusGraph实现事务性存储后端的示例。该示例着重于只有一个数据库的系统,但是正如我们所说,GRIT可以支持由多个数据库组成的系统的ACID事务。
背景
在 微服务 体系结构中,应用程序可以调用多个微服务,通常由不同的团队以不同的应用语言实现这些微服务,并且可以使用多个基础数据库来实现其功能。这种流行的架构为跨多个微服务的一致的分布式事务带来了新的挑战。在微服务环境中支持ACID事务是一个真正的要求,但是使用现有技术很难实现,因为为单个数据库设计的分布式事务机制无法通过微服务轻松扩展到具有多个数据库的情况。
在涉及多个独立数据库的环境中,传统的两阶段提交(2PC)协议1本质上是系统进行分布式事务处理的唯一选择,而无需额外的应用程序工作。但是,由于潜在的许多协调参与者的路径很长,并且在阶段中需要锁定,因此在横向扩展平台中无法很好地工作。另一方面,使用由 诸如Saga之类的框架2执行的事务日志将招致应用程序复杂的补偿逻辑,并且由于不可逆的部分成功的事务而可能产生业务影响。
为了解决这些问题,我们开发了 GRIT ,为全球统一的分布式事务的一个新的协议,它巧妙地结合乐观并发控制(OCC)、2PC和确定性数据库等想法 来实现的,这是一个高性能,具有多个基础数据库的跨微服务的全局一致事务。
GRIT:一种用于分布式事务的协议
下图说明了具有两个数据库的微服务系统中的GRIT协议。中心显示了GRIT组件,包括GTM,GTL,DBTM,DBTL和LogPlayer。
GRIT的关键组件包括:
为了理解协议的细节,我们使用下图显示分布式事务的主要步骤。
在GRIT中,分布式事务经历三个阶段:
总的来说,我们的方法避免了在执行和提交过程中的悲观锁定,并避免了等待物理提交。我们采用乐观的方法,并且通过利用逻辑提交日志并使用确定性数据库技术将物理数据库更改从提交决策过程中移出,从而使提交过程非常高效,这类似于复制中的日志播放。
GRIT能够以最小的协调性为调用微服务的应用程序实现一致的高吞吐量和可序列化的分布式事务。GRIT非常适合很少冲突的事务,并为否则需要复杂机制才能在具有多个基础数据库的微服务之间实现一致事务的应用程序提供关键功能。
将GRIT应用于单个数据库
如您所见,GRIT协议包含两个部分:一个部分用于由DBTM,DBTL和LogPlayer执行的每个数据库(或每个数据库领域,可以是数据库的一组分区),另一部分用于跨数据库协调通过GTM和DBTM。在下图中,我们使用单个数据库的GRIT协议部分说明了 JanusGraph 的事务图存储后端(称为NuGraphStore)的设计。
下图显示了如何使用两个可用区(AZ1和AZ2)部署实施NuGraphStore。
JanusGraph的NuGraphStore后端涉及一些组件:
当应用程序执行事务时,它可以从store中读取并写入store。对于读取操作,存储插件直接与存储服务器通信(除了在事务的写入集中找到的读取之外)。当应用程序在事务上下文中从存储中读取时,存储插件还会跟踪读取集。每次读取的有用信息是<key,lsn>对,其中lsn是反映在读取键值时存储引擎状态的日志序列号。LSN是事务突变条目的日志索引。它由LogStore分配,用于定义后端数据库的快照。未找到的键key被记录为读取集的一部分。与读取不同,用于写入的存储插件不会直接与存储服务器通信。相反,存储插件会在事务的相应写入集中缓冲写入操作。
事务提交时,存储插件将提交的请求和已为事务捕获的读集和写集提交给DBTM。DBTM对事务的OCC执行标准冲突检查。如果没有冲突,它将把写集保留到复制的LogStore中(即,它将写集发送到LogStore副本集,因此所有副本都保持完全相同的日志)。此时,事务提交将在逻辑上完成,并且DBTM将响应存储插件。LogPlayers跟踪LogStore,并根据数据分布将日志条目播放到后端分片服务器。
值得指出的是,以上描述是一种基本设计,具有许多提高性能和可靠性的机会。我们相信,在对各个组件进行优化或对DBTM使用复制以实现更高的可靠性之前,使基本组件成熟将更有生产力。同样,我们可以通过不同的方式捕获读集和写集。对于KV Store,冲突检查所需的最简单形式是<key,lsn>对。但是,为了支持更复杂的系统,该读取集可能包含范围或谓词,如第6章所述。 在撰写本文时,NuGraphStore正在经历开源过程。
点击标题参考原文。