在单体应用向微服务架构转型的过程中,本地事务已不再满足系统一致性需求,为了解决这一问题,前人在对性能和数据一致性反复权衡的过程中总结了许多典型的协议和算法,各有优劣。本文我们将深入探讨 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:易学,易懂,易记,系统设计上无业务侵入,没有额外的编码或测试工作。
多引擎数据库分布式事务技术选型
结合 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 分布式事务过程。
A Service TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
XID 在微服务调用链路的上下文中传播。
TM 向 TC 发起针对 XID 的全局提交或回滚决议。
TC 向 DAL RM 发起全局提交或回滚决议。
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 批量操作,对于相互间有上下文或者依赖的操作并不可用,这限制了它在应用中的使用场景,详细信息请参考 TransactGetItems 和 TransactWriteItems。
DynamoDB 本身没有分布式事务机制,DAL 结合 DynamoDB 功能属性,对提供的插入、更新、删除和查询接口,设计 2PC 机制 来满足 DynamoDB 的 事务属性。
下表显示了分布式事务操作 (DisTxDAL) 和其他操作之间的隔离级别。
更新接口实现方法
一阶段
应用本地事务原子地备份事务记录及备份索引
应用本地事务原子地更新附加事务信息业务值及备份业务原值到镜像表
二阶段
如果决议是提交,应用本地事务原子地移除业务记录事务属性、删除镜像记录、删除备份事务记录
如果决议是回滚,应用本地事务原子地恢复业务记录、删除镜像记录、删除备份事务记录
插入接口实现方法
一阶段
应用本地事务原子地备份事务记录及备份索引
插入带有事务属性信息的业务记录
二阶段
如果决议是提交,应用本地事务原子地移除业务事务属性、删除备份记录
如果决议是回滚,应用本地事务原子地删除业务记录、删除备份记录
删除接口实现方法
一阶段
应用本地事务原子地备份事物记录及备份索引
更新带有事务属性信息的业务记录, 标注为删除操作
二阶段
如果决议是提交,应用本地事务原子地删除事务记录、删除业务记录
如果决议是回滚,应用本地事务原子地恢复业务记录、删除备份记录
查询接口实现方法
事务进行中的数据含有事务属性信息,xid 表示事务全局事务 ID, operation 表示事务操作接口 create、update、delete,这里 item 表示业务数据元素。
操作接口事务开始事务进行事务结束(提交)事务结束(回滚)
基于业务数据变更表及写接口实现方法,实现了在读提交与读未提及查询方法:
判断记录是否含有事务属性,如果无,返回记录,否则到步骤 2
判读隔离级别,如果读提交,步骤 3,如果读未提交,步骤 5
记录事务操作是 create,返回空,否则如果是 delete,去除事物属性信息,然后返回,否则步骤 4
本地事务原子地读取镜像表与业务表,如果镜像表值存在,返回,否则返回业务表值,都不存在返回空
如果记录事务操作是 delete,返回空,否则返回记录
数据访问层事务管理器(DAL TM)实现
为了方便用户使用,分布式事务 API 里封装了与事务协调器及 DAL 资源管理器的交互过程,交互过程对应用是透明的,下面是分布式事务 API:
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 相关的元数据,供应用方按需添加。
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 在数据库访问层分布式事务应用的可行性。