eBay推出首个微服务架构下可实现ACID的分布式事务协议:GRIT

19-10-29 banq
                   

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。

  1. 应用程序:调用微服务以实现其功能。
  2. 微服务(实体服务):构建模块,为应用程序提供面向业务的服务以实现业务逻辑。每个数据库可能支持多个微服务,并且每个微服务可能彼此独立。
  3. 数据库服务:提供数据库读写接口,直接访问数据库服务器。当支持事务时,它还在执行阶段缓存每个事务的读/写结果,并将它们发送到其DBTM以在提交时解决冲突。
  4. 数据库分片服务器:数据库的后端存储服务器,通常进行复制以实现高可用性。

GRIT的关键组件包括:

  1. 全局事务管理器(GTM):它协调多个数据库之间的全局事务。可以有一个或多个GTM。
  2. 全局事务日志(GTL):代表GTM的事务请求队列。GTL中事务请求的顺序确定了全局事务之间的相对可序列化顺序。GTL的持久性是可选的。
  3. 数据库事务管理器(DBTM):每个数据库领域的事务管理器。它执行冲突检查和解决,即本地提交决定位于此处。
  4. 数据库事务日志(DBTL):每个数据库领域中的事务日志,用于记录与此数据库相关的逻辑提交的事务(包括单数据库事务和多数据库事务)。DBTL中的事务顺序确定整个数据库系统的可序列化顺序,包括GTM规定的全局顺序。日志序列号(LSN)被分配给每个日志条目。
  5. LogPlayer:此组件将日志条目按顺序发送到后端存储服务器,以供它们应用更新。每个数据库服务器按顺序应用逻辑提交的事务的日志条目。

为了理解协议的细节,我们使用下图显示分布式事务的主要步骤。

在GRIT中,分布式事务经历三个阶段:

  1. 乐观执行(步骤1-4):当应用程序通过微服务执行业务逻辑时,数据库服务将捕获事务的读集和写集。在此阶段,没有实际的数据修改发生。
  2. 逻辑提交(步骤5-11):一旦应用程序请求事务提交,每个数据库服务点的读集和写集都将提交到其DBTM。DBTM使用读集和写集进行冲突检查,以实现本地提交决策。GTM将收集DBTM针对事务的所有本地决策后,做出全局提交决策。一旦将其写集持久保存在所涉及数据库的日志存储(DBTL)中,就在逻辑上提交事务。这涉及GTM和DBTM之间的最小协调。
  3. 物理应用(步骤12-13):日志播放器异步将DBTL条目发送到后端存储服务器。数据修改在此阶段进行。 

总的来说,我们的方法避免了在执行和提交过程中的悲观锁定,并避免了等待物理提交。我们采用乐观的方法,并且通过利用逻辑提交日志并使用确定性数据库技术将物理数据库更改从提交决策过程中移出,从而使提交过程非常高效,这类似于复制中的日志播放。

GRIT能够以最小的协调性为调用微服务的应用程序实现一致的高吞吐量和可序列化的分布式事务。GRIT非常适合很少冲突的事务,并为否则需要复杂机制才能在具有多个基础数据库的微服务之间实现一致事务的应用程序提供关键功能。

将GRIT应用于单个数据库

如您所见,GRIT协议包含两个部分:一个部分用于由DBTM,DBTL和LogPlayer执行的每个数据库(或每个数据库领域,可以是数据库的一组分区),另一部分用于跨数据库协调通过GTM和DBTM。在下图中,我们使用单个数据库的GRIT协议部分说明了JanusGraph的事务图存储后端(称为NuGraphStore)的设计。

下图显示了如何使用两个可用区(AZ1和AZ2)部署实施NuGraphStore。

JanusGraph的NuGraphStore后端涉及一些组件:

  • 存储插件:一个自定义的存储接口插件,用于在JanusGraph数据库层与后端存储引擎和事务协议组件之间建立接口。
  • DBTM:执行关键冲突检查以实现乐观并发控制。这是执行OCC的单个数据库上GRIT分布式事务协议的一部分  。
  • LogStore:复制的日志存储,用于事务的突变。每个事务一个条目,由日志序列号(LSN)索引。在传统数据库系统中,它充当WAL(预写日志)。LogStore是我们GRIT架构中的DBTL。 
  • LogPlayer:异步将日志条目应用于后端服务器。
  • 存储引擎:后端存储引擎,用于存储JanusGraph中的KCV(键-列-值)数据。它执行读取和变异,并支持LSN定义的快照。

当应用程序执行事务时,它可以从store中读取并写入store。对于读取操作,存储插件直接与存储服务器通信(除了在事务的写入集中找到的读取之外)。当应用程序在事务上下文中从存储中读取时,存储插件还会跟踪读取集。每次读取的有用信息是<key,lsn>对,其中lsn是反映在读取键值时存储引擎状态的日志序列号。LSN是事务突变条目的日志索引。它由LogStore分配,用于定义后端数据库的快照。未找到的键key被记录为读取集的一部分。与读取不同,用于写入的存储插件不会直接与存储服务器通信。相反,存储插件会在事务的相应写入集中缓冲写入操作。

事务提交时,存储插件将提交的请求和已为事务捕获的读集和写集提交给DBTM。DBTM对事务的OCC执行标准冲突检查。如果没有冲突,它将把写集保留到复制的LogStore中(即,它将写集发送到LogStore副本集,因此所有副本都保持完全相同的日志)。此时,事务提交将在逻辑上完成,并且DBTM将响应存储插件。LogPlayers跟踪LogStore,并根据数据分布将日志条目播放到后端分片服务器。

值得指出的是,以上描述是一种基本设计,具有许多提高性能和可靠性的机会。我们相信,在对各个组件进行优化或对DBTM使用复制以实现更高的可靠性之前,使基本组件成熟将更有生产力。同样,我们可以通过不同的方式捕获读集和写集。对于KV Store,冲突检查所需的最简单形式是<key,lsn>对。但是,为了支持更复杂的系统,该读取集可能包含范围或谓词,如第6章所述。 在撰写本文时,NuGraphStore正在经历开源过程。

点击标题参考原文。

 

                   

4