首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

基于MySQL和DynamoDB的强一致性分布式事务实践

在单体应用向微服务架构转型的过程中,本地事务已不再满足系统一致性需求,为了解决这一问题,前人在对性能和数据一致性反复权衡的过程中总结了许多典型的协议和算法,各有优劣。本文我们将深入探讨 Freewheel 如何实现无单点故障的可扩展分布式事务实现模型。

为什么需要分布式事务?

当应用程序有严格的数据一致性要求时,ACID 事务是必须的,如果一个事务涉及的所有操作能够放在一个服务内部,且共用一个数据库,那么只用在一个方法里同一个事务下操作数据库即可。然而为了提升系统整体的可靠性,方便各个模块独立演化,系统从单体应用演进为微服务架构。随着数据体量的增长,数据源也从 MySQL 扩展到关系型数据库 Amazon Aurora 和 NoSQL 数据库(Amazon DynamoDB),基于多样化索引和查询数据的需求,引入了搜素引擎(ApacheSolr 和 ElasticSearch ) ,多服务交互、多数据源并存产生了分布式事务。

Freewheel 分布式事务应用场景有三个:

  • 多服务,同数据源: 业务单元跨越多个独立服务,服务访问同一个数据源,如 MySQL。
  • 单服务,不同数据源: 业务单元涉及一个独立服务,但这个服务访问多个数据源,如 MySQL,DynamoDB。
  • 多服务,不同数据源: 业务单元跨越多个独立服务,每个服务访问不同数据源,如 MySQL,DynamoDB。

综合考虑 Freewheel 的业务需求后,我们实现了多引擎数据库分布式事务。

多引擎数据库分布式事务设计

Freewheel 分布式事务方案主要设计目标如下:

数据强一致性:确保该事务范围内的所有操作都可以全部成功或者全部失败,事务具有原子性、一致性、隔离性、持久性 4 个特性。

  • Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态,就像这个事务从来没有执行过一样。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。完整性包括外键约束、应用定义等约束不会被破坏。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

系统高可用:遵循“design for failure”的设计原则,硬件层面,采用服务节点多 region 多 AZ 部署和节点故障快速自恢复的策略来保证系统的高可用。软件层面,设计 failover 机制应对服务异常。

可扩展:应用 Auto Scaling 服务,它会基于设定的负载压力,自动进行扩展和缩容,来保证服务正常运行。

易用性:分布式事务应用 API:易学,易懂,易记,系统设计上无业务侵入,没有额外的编码或测试工作。

多引擎数据库分布式事务技术选型

常见分布式事务解决方案对照表:

方案

优点

缺点

XA

强一致性无业务侵入

同步阻塞有限的数据源支持,比如MySQL, ActiveMQ5.7.7版本前,客户端退出或者服务宕机事务回滚,服务重启binlog丢失问题

两阶段提交

强一致性

延迟:同步阻塞单点问题:协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作参与者依赖:任意一个节点失败就会导致整个事务失败

补充事务(TCC)

高可靠实时高

开发复杂,需带有业务补偿机制事务状态管理,需要多次DB操作,有一定的性能损失最终一致性

Saga

易于理解松耦合,避免服务间依赖关系实时高

开发复杂,需带有业务补偿机制最终一致性

Seata

高性能易于使用支持AT, TCC, SAGA, XA 模式

改造成本大:相关程序都要加入Seata事务性能损失:Seata本身存在一定的性能损耗

  • 强一致性
  • 无业务侵入
  • 同步阻塞
  • 有限的数据源支持,比如MySQL, ActiveMQ
  • 5.7.7版本前,客户端退出或者服务宕机事务回滚,服务重启binlog丢失问题

两阶段提交

  • 强一致性
  • 延迟:同步阻塞
  • 单点问题:协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作
  • 参与者依赖:任意一个节点失败就会导致整个事务失败

补充事务(TCC)

  • 高可靠
  • 实时高
  • 开发复杂,需带有业务补偿机制
  • 事务状态管理,需要多次DB操作,有一定的性能损失
  • 最终一致性

Saga

  • 易于理解
  • 松耦合,避免服务间依赖关系
  • 实时高
  • 开发复杂,需带有业务补偿机制
  • 最终一致性

Seata

  • 高性能
  • 易于使用
  • 支持AT, TCC, SAGA, XA 模式
  • 改造成本大:相关程序都要加入Seata事务
  • 性能损失:Seata本身存在一定的性能损耗

结合 Freewheel 强一致性业务需求,多数据源分布式事务将由 XA、2PC 和 Seata 这些解决方案组合而成。

  • Seata 框架设计思想
  • 基于 XA 的 Aurora 分支事务
  • 基于 2PC 的 DynamoDB 分支事务

多数据源分布式事务解决方案

架构解析

Freewheel 分布式事务依托在 Freewheel 数据访问层中间件(DAL)上,这个中间件是由 Freewheel 平台团队自主研发的,它的目标是为上游应用提供更好的数据访问,为下游数据源提供更好的保护。为了方便描述,下文均用 DAL 来作为 Freewheel 数据访问层中间件的简称。

分布式事务由这三个组件来协商处理:

  • Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
  • Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
  • Resource Manager (RM): 控制分支事务,负责分支注册、并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

为了预防死锁,并且减少 DAL RM 和 TC 之间的 交互,降低对 TC 的依赖,同一个分布式事务操作放在同一个 DAL 节点,由此,DAL RM 可以方便的在单节点控制和协调分支事务,完成全局事务的提交和回滚。

以多服务,不同数据源(Aurora 与 DynamoDB)为例,描述 Freewheel 分布式事务过程。

  1. A Service TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  2. XID 在微服务调用链路的上下文中传播。
  3. TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  4. TC 向 DAL RM 发起全局提交或回滚决议。
  5. DAL RM 对 XID 下管辖的全部分支事务完成提交或回滚请求。

数据访问层资源管理器(DAL RM)实现

基于业务需求,DAL 分布式事务支持的数据源为 MySQL 和 AWS DynamoDB,下面章节阐述了这两个数据源 ACID 技术实现。

分布式事务设计中新建了事务控制表、事务记录表、索引表及业务镜像表:

  • 事务控制表:记录事务运行状态开始、提交、回滚。
  • 事务记录表:存储正在发生事务信息。
  • 索引表:记录记录主键 ID, 用于实现排它性对其他事务更新与普通更新。
  • 业务镜像表:存储业务原值。

Aurora/MySQL

采用 MySQL XA 2PC 来保证 ACID,原因如下:

  • Mysql 5.7 版本已经支持 XA,目前 Aurora 也是 Mysql 5.7.x。
  • XA 强一致性。
  • 不侵入业务,这会减少业务方的工作量。

这个时序图描述了 RM 对 MySQL 事务的工作流程:

 一个事务操作,由同一个 DAL RM 处理,相同 DB 下业务事务处理,放在一个 XA 操作里:

  • 节省 XA 连接 fd。
  • 减少 DAL RM 和 Aurora 之间的 XA 交互次数。
  • 避免多个 XA 分支事务上的数据操作冲突。

SQL CRUD 语句应该使用触发行锁的索引操作,否则会触发表锁,影响系统吞吐量。

AWS DynamoDB

DynamoDB 提供了本地事务接口 TransactGetItems 和 TransactWriteItems, 它等效于 MySQL 批量操作,对于相互间有上下文或者依赖的操作并不可用,这限制了它在应用中的使用场景,详细信息请参考 TransactGetItemsTransactWriteItems

DynamoDB 本身没有分布式事务机制,DAL 结合 DynamoDB 功能属性,对提供的插入、更新、删除和查询接口,设计 2PC 机制 来满足 DynamoDB 的 事务属性。

下表显示了分布式事务操作 (DisTxDAL) 和其他操作之间的隔离级别。

Operation

Other Operation

Isolation Level

DisTxDAL

DisTxDAL

可序列化

DisTxDAL

DAL DDB write interface

可序列化

DisTxDAL

DAL DDB read interface

读取已提交|读取未提交 (可选)

更新接口实现方法

一阶段

  • 应用本地事务原子地备份事务记录及备份索引
  • 应用本地事务原子地更新附加事务信息业务值及备份业务原值到镜像表

二阶段

  • 如果决议是提交,应用本地事务原子地移除业务记录事务属性、删除镜像记录、删除备份事务记录
  • 如果决议是回滚,应用本地事务原子地恢复业务记录、删除镜像记录、删除备份事务记录

插入接口实现方法

一阶段

  • 应用本地事务原子地备份事务记录及备份索引
  • 插入带有事务属性信息的业务记录

二阶段

  • 如果决议是提交,应用本地事务原子地移除业务事务属性、删除备份记录
  • 如果决议是回滚,应用本地事务原子地删除业务记录、删除备份记录

删除接口实现方法

一阶段

  • 应用本地事务原子地备份事物记录及备份索引
  • 更新带有事务属性信息的业务记录, 标注为删除操作

二阶段

  • 如果决议是提交,应用本地事务原子地删除事务记录、删除业务记录
  • 如果决议是回滚,应用本地事务原子地恢复业务记录、删除备份记录

查询接口实现方法

事务进行中的数据含有事务属性信息,xid 表示事务全局事务 ID, operation 表示事务操作接口 create、update、delete,这里 item 表示业务数据元素。

操作接口

事务开始

事务进行

事务结束(提交)

事务结束(回滚)

create

None

item: item valueoperation:createxid: xid value

item: item valueoperation:createxid: value

None

update

item: item value

item: item new valueoperation:updatexid: xid value

item: item new valueoperation:updatexid: xid value

item: item value

delete

item: item value

item: item valueoperation:deletexid: xid value

None

item: item value

基于业务数据变更表及写接口实现方法,实现了在读提交与读未提及查询方法:

  1. 判断记录是否含有事务属性,如果无,返回记录,否则到步骤 2
  2. 判读隔离级别,如果读提交,步骤 3,如果读未提交,步骤 5
  3. 记录事务操作是 create,返回空,否则如果是 delete,去除事物属性信息,然后返回,否则步骤 4
  4. 本地事务原子地读取镜像表与业务表,如果镜像表值存在,返回,否则返回业务表值,都不存在返回空
  5. 如果记录事务操作是 delete,返回空,否则返回记录

数据访问层事务管理器(DAL TM)实现

为了方便用户使用,分布式事务 API 里封装了与事务协调器及 DAL 资源管理器的交互过程,交互过程对应用是透明的,下面是分布式事务 API:

代码语言:javascript
复制
type DistributedTransApi interface {        DisTxDAL(ctx context.Context, fn TranFunc) error}type TranFunc func(ctx context.Context) error

复制代码

微服务之间,微服务与数据库访问层之间采用 google rpc 调用,服务之间关键数据都是基于 context metadata,如果经过两层服务交互,就会导致 context metadata 丢失。举例来说,A 服务调用 B 服务,B 服务调用 C 服务,那么 C 服务就会缺失 A 服务 context metadata,针对这种情况,DAL 提供了通用函数用于提取 DAL 相关的元数据,供应用方按需添加。

代码语言:javascript
复制
func ExtractDalMetadata(ctx context.Context) (metadata.MD, error)

复制代码

数据访问层事务协调器(DAL TC)实现

协调器主要功能点:

  • 分配事务 XID,维护全局事务的运行状态,负责协调和驱动全局事务的提交或回滚;
  • 故障转移:提交/回滚。

分配事务 XID

全局分配唯一事务 ID,供 DAL TM 获取,此数据需要在同一事务的业务服务间传输共享。

事务协调

维护全局事务的运行状态,负责协调和驱动全局事务的提交或回滚。

故障转移

DAL TC 是 HA 多节点实例,引入 ETCD leader 选举机制来保证只有一个 TC 实例承担 failover 功能,详细信息在系统高可用章节软件层面 failover。

系统高可用

硬件层面

为了预防硬件故障对高可用的影响,DAL,TC 和 ETCD 服务均是多 Region 多 AZ 部署,并且基于服务的特性配置了相应的服务策略:

  • ETCD 采用自恢复策略
  • DAL 和 TC 服务采用了弹性伸缩策略

美东美西均部署相应服务作为服务灾备策略,下图是美东地区的解释图(美西类似)。

软件层面 Failover

在 DAL 应用或者业务应用遇到异常退出时,软件层面 Failover 机制是为了能不发生死锁,并且继续处理未完成分布式事务,实现方法如下:

  • DAL TM 接口添加超时控制,由应用设置事务的超时时间,默认是 60 秒
  • DAL RM 在事物开始、提交或者回滚阶段存储 xid 信息,如过期时间,运行状态等,在业务接口调用里存储业务处理记录
  • DAL TC 轮询地从事务控制表里获取超时事务。基于事务状态处理:如果 start or rollback:触发 Rollback,如果 commit:触发 commit

未来展望

Freewheel 强一致性分布式事务未来会支撑更多的数据源,如 Redis、Solr 和 ElasticSearch 等,目前的数据库访问层 API 是基于 gRPC,这对数据库访问层使用方带来了一定技术语言限制,未来会探究 GraphQL 在数据库访问层分布式事务应用的可行性。

作者简介:

李长城,Lead Software Engineer,目前就职于 Comcast FreeWheel 架构平台团队。研究方向为微服务架构、数据库中间件、云计算等领域。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/vo46BP1DR6MAJzWOPOmf
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券