背景:企业业务体量越来越大时候,现有的架构已经不能满足企业快速需要,软件架构必然面临升级,
当下最流行微服务架构,把业务系统功能进行拆分,满足系统并发性、可用性等。业务拆分面临着把原来一个完整独立功能,
变成有两个或两个以上应用来完成,应用之间需要彼此通讯完成功能,由于网络不可靠性以及多应用之间处理失败情况,
会导致事务不完整性,出现概率性数据不准确性,分布式事务主要解决这种情况。
关于微服务架构网上资料很多,可自行查阅,本文主要介绍微服务之分布式事务。
什么是分布式事务 指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性
BASE理论是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,
BASE是对CAP中一致性和可用性权衡的结果,其核心思想是及时无法做到强一致性,但每个应用都可以根据自身的业务特点,
采用适当的方式使系统达到最终一致性。
优点:原理简单,实现简单
缺点:同步阻塞、单点问题(协调器单点)、性能较差
优点:降低了参与者阻塞范围,并且能够在出现单点故障后继续达成一致
缺点:如果网络出现分区,此时协调者所在的节点和参与者无法进行正常网络通信,这时,参与者依然会进行事务提交,这必然导致数据不一致
TCC(Try-Confirm-Cancel),具体描述是将整个操作分为三步。
两个微服务间同时进行Try,在Try的阶段会进行数据的校验,检查,资源的预创建,如果都成功就会分别进行Confirm,
如果两者都成功则整个TCC事务完成。如果Confirm时有一个服务有问题,则会转向Cancel,相当于进行Confirm的逆向操作。
例如:以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,
则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,
复杂度也不一样,因此,这种模式并不能很好地被复用。
TM相关
com.alibaba.fescar.tm.api.TransactionalTemplate
RM相关
com.alibaba.fescar.rm.datasource.exec.SelectForUpdateExecutor
com.alibaba.fescar.rm.datasource.ConnectionProxy
com.alibaba.fescar.rm.datasource.exec.AbstractDMLBaseExecutor
com.alibaba.fescar.rm.RMHandlerAT
TC相关
com.alibaba.fescar.server.coordinator.DefaultCoordinator
com.alibaba.fescar.server.coordinator.DefaultCore
com.alibaba.fescar.server.lock.DefaultLockManager
关于当前事务通过seata回滚到before Image快照,别的线程更新改数据,如何处理这种情况.
本地事务【读已提交】,fescar全局事务【读未提交】。这是这段话的核心。
我理解的这段话中fescar全局事务读未提交,并不是说本地事务的db数据没有正常提交,而是指全局事务二阶段commit|rollback未真正处理完(即未释放全局锁)。
总结来说:全局未提交但是本地已提交的数据,对其他全局事务是可见的【当然在本地事务提交后,本地事务提交前,隔离级别是本地事务的管辖范围】
例子
产品份额有5W,A用户购买了2万,份额分支一阶段完毕(本地事务份额已经扣除commit),但是在下单的时候异常了。
因为本地事务读已提交,这时候fescar允许业务访问该条数据,3W,在A用户的份额分支未回滚成功前,对其他用户可见。
但是其他用户并不能买该产品,必须等到产品份额回滚到5万,其他用户才可以操作产品数据。所以看了这个例子 真的有必要做到全局事务读已提交么?
undolog的数据结构
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "172.168.0.38:8091:2018531455",
"branchId": 2018531456,
"sqlUndoLogs": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "UPDATE",
"tableName": "t_asset",
"beforeImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_asset",
"rows": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": 12,
"value": "DF001"
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "amount",
"keyType": "NULL",
"type": 3,
"value": ["java.math.BigDecimal", 1]
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "voucher_code",
"keyType": "NULL",
"type": 12,
"value": "e2d1c4512d554db9ae4a5f30cbc2e4b1"
}]]
}]]
},
"afterImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "t_asset",
"rows": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Row",
"fields": ["java.util.ArrayList", [{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "id",
"keyType": "PrimaryKey",
"type": 12,
"value": "DF001"
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "amount",
"keyType": "NULL",
"type": 3,
"value": ["java.math.BigDecimal", 2]
}, {
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "voucher_code",
"keyType": "NULL",
"type": 12,
"value": "e2d1c4512d554db9ae4a5f30cbc2e4b1"
}]]
}]]
}
}]]
}