场景
有如下业务场景:当我们添加订单时,要扣减相应的库存,同时用户积分也要增加,支付服务做相应的支付功能。
涉及微服务如下:
订单微服务、商品微服务、积分微服务、支付微服务。
问题:在调用完商品微服务扣减库存后,如果库存扣减成功,订单微服务出现异常,必然导致订单添加失败,导致订单本地事务回滚,而商品微服务扣减库存的本地事务已经提交,这就是问题了。
介绍
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务,Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
传统事务
在传统的,巨大的项目中,如下,事务由3个模块构成,开发人员只使用单一的数据源,自然地,数据的一致性就被本地事务所保证。
在微服务架构中,事情变得复杂了,以上的3个模块分别在不同的数据源上,每个数据源各自只能保证各自模块的数据一致性。
Seata分布式事务
针对以上问题,Seata是如何处理这种问题的呢?
Seata把一个全局事务,看做是由一组分支事务组成的一个大的事务,分支事务可以直接认为就是本地事务。
三个组件
-
Transaction Coodinator(TC)
事务协调者,维护全局事务和分支事务的状态,驱动全局事务的提交或回滚
-
Transaction Manager(TM)
事务管理器,定义全局事务的范围:何时开始全局事务,何时提交或回滚全局事务,tc只负责开全局事务,由TM发通知给TC,当所有的微服务都调用完毕后,再次发消息给TC,通知TC处理
-
Resource Manager(RM)
资源管理器(数据库),负责向TC注册分支事务、向TC汇报状态,并接收事务协调器的指令。驱动分支事务的提交或回滚,每个RM会把本地事务执行结果以及微服务的ip和端口发送到TC,告知TC
流程
- TM告知TC开启一个全局事务,TC生成一个全局唯一的XID。
- XID在微服务调用链中传播。
- RM向TC注册分支事务,将其纳入XID对应的全局事务的管辖之内。
- TM告知TC发起针对XID的全局提交或回滚决议。
- TC调度XID下管辖的全部分支事务完成回滚请求。
设计思路(AT模式)
AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如图
第一阶段
业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undolog,并同时入库,这是怎么做的呢?先抛出一个概念DataSourceProxy代理数据源,通过名字大家大概也能基本猜到是什么个操作,后面做具体分析
第二阶段
分布式事务操作成功,则TC通知RM异步删除undolog
分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
整体执行流程 :
亮点
相比与其它分布式事务框架,Seata架构的亮点主要有几个:
-
应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
-
将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
-
通过全局锁实现了写隔离与读隔离。
存在的问题
性能损耗
一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。
性价比
为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应用开发一个补偿交易是否是值得?
全局锁
相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销比XA的prepare低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点数据,这个问题会更加严重。
回滚锁释放时间
Seata在回滚时,需要先删除各节点的undo log,然后才能释放TC内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。
死锁问题
Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。
分布式事务的特点:ACID
-
原子性(Atomicity): 原子性表示事务中的所有操作要么全部成功完成,要么全部失败回滚,不会出现中间状态。这意味着如果一个事务的任何部分操作失败,整个事务都会被回滚,保证数据库的一致性。
-
一致性(Consistency): 一致性确保在事务开始之前和结束之后,数据库从一个一致的状态转换到另一个一致的状态。这意味着事务执行前后,数据库的完整性约束(例如主键、外键约束)仍然保持有效,不会破坏数据的完整性。
-
隔离性(Isolation): 隔离性表示多个事务可以同时执行,但它们之间不会相互干扰,各个事务之间是隔离的。每个事务应该感觉不到其他事务的存在。隔离级别定义了事务之间的可见性和影响范围,例如,读未提交、读已提交、可重复读和串行化。
-
持久性(Durability): 持久性确保一旦事务提交,其结果将被永久保存,即使系统崩溃或发生故障。数据库系统会将事务的变更写入持久存储,以确保在重启后可以恢复到提交事务后的状态。
这四个特性组合在一起,形成了事务的 ACID 特性,是确保数据库事务的正确性、可靠性和稳定性的基础。在数据库中,事务的正确性是至关重要的,因为它们涉及到数据的一致性和可靠性。