用 Seata 搞定分布式事务的规范化建设-赋能产研|提质增效原创
一、背景
以下图购买商品下单业务为例,下订单由订单服务、账户服务和库存服务 3 个服务来完成。
本地事务只能保证每个服务各自的本地事务,比如库存服务 DB 操作异常,其通过本地事务回滚后库存数据无异常,但订单和账户的记录并未回滚,因为他们并不知道库存异常了,这便是典型的分布式数据一致性问题。
在生产环境中设备、程序等都是不可靠的,若它们发生意外则会出现数据不一致的情况;在对数据的一致性要求高的业务场景中,若缺少有效工具的辅助,则常常需要通过如客诉、人工抽检核对等方式来发现问题,之后再交给程序员通过人工智能来排查、修复;这种方式虽也能解决问题,但从时效和人力两方面看都很低效,同时也拉低服务质量。
虽然 BASE 论文基于 CAP 定理分析并表述了分布式系统追求高可用的场景下不得不弱化强一致性的观点,并对众人产生了深远的影响,但并不能理解为要降低对一致性的要求,尤其是在某些特殊业务场景中,数据容易出现非预期的不一致或高延迟的不一致时,会给客户带来极其不好的使用体验,严重时甚至导致运营无法推进、产品无法落地。所以需要重新审视数据一致性问题,它实际是与系统的可扩展性和高可用同等重要 是【基础 IT 架构】支撑业务高质量转型、升级的重点也是难点。
二、诉求与选型
应对业务规模、数据规模的不断扩大,主流技术方案都是通过横向扩展节点以提高非热点数据的并发性能;而横向扩展节点的效果体现在如下 2 方面:
-
扩展功能节点:对应用进行微服务化改造。 -
扩展数据节点:增加数据分片如分库分表,使用混合存储如 mysql + ES 的组合等。
微服务演进方案的优势很多,但同时也必然会带来业务数据不一致的问题,有些情况还让人特头疼。在之前很长一段时间,受分布式事务技术成熟度、业务的复杂性及实现性价比等多重因素的影响,关于该如何应对分布式业务数据一致性的这个难题,似乎分化成两种相对的观念:一种是困难太多、性价比不高,一致性保障让人有心无力,进而置之不理;另一种则是需迎难而上,各显神通,导致出现了许多有意思的变种实现,有些可能既不通用也不健壮。
从标准架构规范的大局来看,置之不理或各显神通这两种情况都非合理状态,实际上需要的是一套规范的、通用的、可靠的分布式事务解决方案,能够帮助开发者在分布式的环境下,既能保证业务数据的一致性,又不需要投入太多的资源用于业务数据一致性的开发维护,还能加快迭代速度保障项目交付。
分布式事务解决方案通常分为两大类,其中一类是异步确保型,另一类是非异步确保型;异步确保型非本篇主题,暂且不提。重点探讨非异步确保型,在行业中开源产品有Seata
、tcc-transaction
、TX-LCN
、
Hmily
、ByteTCC
、EasyTransaction
等,它们各有特色后续会整理一篇稍详细点的能力对比介绍。从它们所提供方案的分类来说有:补偿型-TCC 、无侵入性-AT、强一致型-XA 、补偿性-SAGA 以及LCN。Seata 的能力比较全面,所提供方案种类最多,而且社区活跃度最高。
Seata 从设计层面将事务参与者的角色分为 TC、RM、TM,事务的整体运转模型如上图,基于此模型,Seata 支持了TC、AT、XA、SAGA 这 4 种方案,能应对许多应用场景,个人感觉比较贴合日常分布式事务能力诉求的的是 TC 集群的高可用架构 以及 TCC 和 AT 这两种事务模式的能力。
1)TC 集群具有高可用架构
应用到集群是这样一个间接的关系:应用 -》事务分组 -》TC 集群,应用启动后所指定的事务分组不能变,可通过配置中心变更事务分组所属的 TC 集群,Seata 客户端监听到这个变更后,会切换到新的 TC 集群。
2)TCC 模式
-
TCC 模式要求由业务侧通过编码实现 Try、Confirm、Cancel 3 个方法,TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 操作作为二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。
-
因为各阶段都由业务侧编码实现,所以就大大增加了灵活性,容易实现特定场景下的自定义优化和特殊功能开发。这个模式几乎能满足任何想要的事务场景,如补偿型、资源预留型以及消息事务型等。但也意味着开发的工作量比较大,对开发者的要求比较高。
-
无论有没有本地事务控制都可以使用该模式,与底层数据库事务实现不相关(无需 JDBC 支持)。
-
Seata 对 TCC 模式实现的完整度比较高,可应对经典的空回滚、防悬挂和幂等控制这三个问题。
3)AT 模式
-
若把 TCC 模式类比成手动挡驾驶模式,那么 Seata 的 AT 模式就好比自动挡驾驶模式,用户配置下数据源代理,在需要全局事务的方法上添加注解即可,就是这么简单。AT 模式是基于 DataSource 代理实现的,其核心思想就是由框架托管分布式事务:通过代理 DataSource ,拦截 SQL 执行,增强其执行逻辑,由代理侧加入额外的能力以完成分布式事务(好似钢铁侠的机甲提供了超强能力)。
-
要求上下游分支事务都支持 JDBC,且所用的 SQL 需要框架能够支持,有一定的性能损耗,在应用层增加了全局锁(行级锁),高并发有热点数据的场景需斟酌。
-
在业务并发不高、逻辑复杂、对一致性要求较高的场景中特别有优势,只需要引入依赖后调整少量代码,方法上增加注解便可拥有完整的分布式事务的能力;这种场景下,特能体现 AT 模式难度低、效率高、更安全的特点。
基于以上评估,Seata 所兼备的 TC 高可用、 TCC 模式和 AT 模式特别符合一套规范的、通用的、可靠的分布式事务解决方案的诉求,能将分布式事务问题从业务中尽量剥离出来(AT模式剥离的比较彻底),作为一个独立的技术切面由专人管理运维(独立的服务端和模块化的客户端),提供简单、易用、高效、稳定的分布式事务解决方案,体现出以下价值:
-
架构复杂度降低:分布式事务这个切面的技术问题,全部由 Seata 所提供的服务能力来解决。 -
设计和开发成本减轻,加快交付和迭代速度:业务逻辑的设计和开发中,若采用 AT 模式,则无需考虑是否涉及分布式事务,是否需要做额外的准备,专注 SQL 编写实现业务逻辑即可,对业务 0 侵入 。 -
运营成本降低,保证业务数据准确度,以及出错时的自动回滚和及时通知
三、如何推进
当下互联网大环境不太好(盲猜下半年开始好转),大多公司会调整为业务导向,因此基础技术领域的部门需重新审视自己的角色、调整自己职责,不能以技术至上的心态一味追求高精尖,而偏爱技术的个体也需调整思路,全力转向为开发者服务,赋能产研。在老板业务导向的方针下,新技术的引入、推进就不能再纯粹以技术视角考量,要结合业务,评估是否有痛点,是否有需求。
通常 PMO 与业务部门组织需求和使用场景的沟通后,会得到比较一致的反馈,即较多同事有听到过 Seata 在其他公司的实践分享,但此技术的可用性、稳定性方面在自家公司尚无案例借鉴,无法给予充足的信任,即使有需求也不敢贸然明确提出使用 Seata 就能解决问题;他们会希望基础设施先行,给出兼容性、性能、稳定性等方面的一些可靠结论。新能力需要业务方提需求,业务方要基础设施先提供能力保障,这个“死锁现象”按照工程化的思路重新梳理,可以跟 PMO 商定徐图缓进的应对之策:
-
加强对 Seata 技术层的掌控力,针对公司的技术栈和数据库中间件进行全面的功能性验证,明确其可靠能力的合集;在此过程中梳理原理、解决问题并将验证结论等进行整理 -
通过宣导的方式,进行同步、培训和引导,互动的过程中,搜集需求和意向用户以及其需求场景 -
理清 Seata 的安全保障机制,以 最小测试单元的原则来保障试点应用的接入和验证。
TCC 模式对代码的侵入性较高,需要做大量的改造,这对 Seata 的推进并不友好,而 AT 模式能适配的应用场景很多,只需要少量代码的微调即可,并且通过 AT 模式即也能证验证大部分 Seata 的能力。基于以上考虑,可以将尝试的方向先聚焦到 AT 模式上,有了 AT 模式的技术积累沉淀后,TCC 模式的应用将会水到渠成
四、探索 AT 模式
4.1 AT 模式业务无侵入的的原理
1)一阶段:
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其读出保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其读出保存成“after image”,之后生成事务行锁信息并向 TC 申请持有锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
2)二阶段-提交:
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需立即将一阶段的事务行锁释放后(尽早释放锁以提升性能),再将对应”before | after image“数据的清理即可(因为本地事务和全局锁都已释放,此环节可采用更高性能的异步处理)。
3)二阶段-回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
AT 模式的一阶段、二阶段提交\回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
4.2 AT 模式的兼容性验证
针对公司的技术栈做兼容性验证,需主要关注两方面:一是 RPC 框架,如 Dubbo 的事务ID透传能力;二是公司的数据库组件,需考虑如单库、读写分离、分库分表以及动态数据源切换等场景下的兼容性。基于这些情况在自测阶段开发一套分布式的应用,用以验证各种场景下 Seata AT 模式的兼容性情况。
1)RPC 中需保障全局事务 ID 的透传能力
-
分布式事务其实现机制中会有全局事务标识,即 xid,xid 需随着 RPC 请求传递给各分支事务;下游服务也是根据请求上下文中是否有 xid 来判断自己是否开启分支事务
2)数据库组件可从如以下几个方面设计用例:
用例 | 兼容性 | 描述 |
---|---|---|
批处理 | ||
事务隔离性 | 要使用 Seata 提供的应用层全局行锁机制,避免脏写,保障读已提交 | |
独立主库 | ||
读写分离 |
3)分库分表的情况有些特殊
-
分库分表中间件的实现通常是通过增加一层逻辑 DataSource 如名为 ShardingDataSource ;在其内部维护着一批物理(物理是形容与真实DB保持物理链接) DataSource,在接收 SQL 后,解析 SQL 并计算路由之后将 SQL 转交给对应的物理 DataSource 来执行
-
Seata 所需的元数据、一阶段 UndoLog 的写表和二阶段删除 UndoLog 或执行数据回滚,这些能力的实现依赖的是物理 DataSource ,因为 Seata 无法得知(也无需参与)如何解析 SQL、如何匹配路由规则、如何选择库表;另外 Seata 的自动代理处理是无法识别 DataSource 是不是物理 DataSource
-
具体如何改造呢?对借鉴业界类似产品如 ShardingSphere ,将其对接 Seata 的实现机制进行梳理分析,即可明确方案:对数据库中间做改造,核心是针对物理 DataSource 做代理,而非逻辑 DataSource,原理如下图:
4.3 小结
五、试点
5.1 诉求及顾虑
若经过宣导后,有业务团队表示希望通过接入 Seata 来解决其业务数据一致性的问题,那么积极配合业务团队的开发、测试同学做评审,让他们详尽的表述诉求,通过沟通确认 Seata 的什么模式适合他们的业务场景 。另外特别提醒,要收集他们的顾虑之处,通常会包含如下这些:
-
Seata 的事务能力能否按环境生效(开发、测试生效,留足观察周期;生产先不生效) -
新业务需求的实现依赖分布式事务,且业务边界很清晰,能否针对特定请求生效,避免污染原始业务,因为全面回归测试的成本很高 -
AT 模式能否支持按照业务状态进行强制回滚 -
Seata 是否有运行期开关和动态降级能力 -
AT 模式的性能损耗是怎样
总结来说即 Seata 是否提供了足够全面的安全保护机制,让新能力试点能够满足最小化测试的原则,并在突发情况下能够自动降级或快速人工关闭。
5.2 梳理安全保障措施
1)Seata 事务能力能否按环境生效
-
Seata 有启动开关,可在开发、测试环境打开,生产环境关闭;如此便可有足够的观察期
2)能否针对特定请求生效
-
AT 模式是基于数据源增强来实现的,若要避免对原始业务有干扰,可通过 DataSource 隔离的方式来实现,即:
-
启用分布式事务的逻辑使用 Seata 增强的数据源 -
不启用分布式事务的逻辑,使用原始数据源
3)AT 模式能否支持按照业务状态进行强制回滚
-
方法上标注注解的方式下,当业务状态不满足预期需要回滚时,可通过显示的抛出异常来实现全局事务回滚 -
显式调用全局事务 API 方式下,可根据业务状态来决定是执行提交还是执行回滚
通过这两种方法也都可以实现相同方法中,根据入参的属性值来决定是否启动分布式事务。
4) Seata 是否有运行期开关和动态降级能力
-
Seata 内部提供了通过配置中心控制运行时全局事务的禁用或开启的能力 -
Seata 内提供了基于滑动时间窗口和阈值判断的动态降级管控能力 -
也可通过 AOP+显式调用全局事务 API 的方式,配合公司内的流控防护系统实现降级管理
5.3 AT 模式的性能损耗是怎样
关于 AT 模式的性能损耗,个人认为这个结论的给出会是模糊、不具体的,全局事务和分支事务的实现会有固定的 RPC 请求、SQL 查询和记录存储等操作,所以关于一次分支事务的额外损耗,其计算逻辑是很清晰的。但一个完整的分布式事务的耗时,受到其分支事务的总个数影响,受每个本地事务中的数据规模和读写逻辑影响,也受如热点数据的密度变化等多方面的影响。
AT 模式的性能到底该如何评判呢?我认为场景化、针对性的测试结果固然非常重要,而无微不至的可观性支撑也非常有效。全链路追踪系统 SkyWalking 为 Seata 提供了插件,结合其 JDBC 插件,可以看到全局事务、每个分支事务以及全部 SQL 执行的流程和耗时详情,如下图所示:
分布式事务的整体性能数据是由诸多分支事务的性能累加而得;有了完整 Trace 信息的支撑,有的放矢的优化有问题的环节(可能是架构设计的问题,也可能是表设计的问题,也可能是 SQL 语句的问题...),那整个事务的性能问题自然就不存在了。
AT 模式有其特定的适用场景,若业务场景不合适,也可考虑 Seata 所提供的更为灵活的 TCC 模式。当然除 Seata 提供这些模式之外,也还有其他的异步确保型方案。是否使用分布式事务以及使用哪种分布式事务方案需因地制宜。
六、本地化建设与推进
6.1 管理端的建设
-
与数据库自动化运维平台打通
-
自动初始化支撑 AT 模式的表 -
undolog 异常记录的查看与管理 -
事务历史记录的留存、查看与统计
-
异常事务的运维管理
-
当事务处理异常时主动通知相关方 -
提供便捷查看异常事务相关上下文的能力 -
提供人工终止异常事务的能力 -
TC集群适配双活机制
6.2 业务域边界的划分与隔离保障
试点过程中虽然不太需要考虑这个问题,但一旦接入的应用变多以后,需要充分考虑并规划好 TC 集群所接入的业务域中应用的规模,以及业务域边界的划分和隔离管理。
6.3 推进
-
多跟项目团队沟通收集需求场景 (如采用分库分表的项目可能天然缺失一致性保障),适合 AT 模式的场景可快速接入。 -
通过较为健壮的 TCC 模式 来替代那些 历史遗留的仅有预检机制却无回滚机制的事务实现。
七、总结
通过持续深入探究 Seata 的能力、做全面的兼容性验证和技术宣导,让同事们对 Seata 有进一步的认知和信任。与项目试点同行,加快 Seata 在公司的本地化适配与能力建设,并与社区保持密切的沟通。在遵守安全合规的前提下,可积极参与社区建设,向社区同步、交流实践历程和效果,会得到同行者高质量的建议与指导,少走弯路。
我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【架构染色】进行交流和学习
参考与感谢
Seata 新特性,APM 支持 SkyWalking
分布式事务--TCC 的中间件--选型/对比
分布式事务 Seata 及其三种模式详解 | Meetup#3 回顾
通过 AOP 动态创建/关闭 Seata 分布式事务