微服务实战 07 seata AT模式

在这里插入图片描述

seata AT 模式原理

AT 模式时基于XA事务模型研究而来

  • 第一阶段:业务数据和回滚记录在同一本地事务中提交,释放本地锁和连接资源
  • 第二阶段:提交异步化,非常快速地完成。回滚通过第一阶段的回滚日志进行反向补偿

AT 的两个阶段

第一阶段

在业务流程中进行操作数据库时,Seata会给予数据源代理对原执行的SQL进行解析。
然后将业务数据在更新前后保存到 undo_log 日志表中,利用本地事务的ACID特性,把业务数据的更新和回滚日志写入同一个本地事务中进行提交
在这里插入图片描述
我把 rollackInfo 字段保存的值转成字符进行查看

{
    "@class":"io.seata.rm.datasource.undo.BranchUndoLog",
    "xid":"192.168.137.1:8091:3080761116565270529",
    "branchId":3080761116565270532,
    "sqlUndoLogs":[
        "java.util.ArrayList",
        [
            {
                "@class":"io.seata.rm.datasource.undo.SQLUndoLog",
                "sqlType":"INSERT",
                "tableName":"sys_order",
                "beforeImage":{
                    "@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords",
                    "tableName":"sys_order",
                    "rows":[
                        "java.util.ArrayList",
                        [

                        ]
                    ]
                },
                "afterImage":{
                    "@class":"io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName":"sys_order",
                    "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":"PRIMARY_KEY",
                                            "type":-5,
                                            "value":[
                                                "java.lang.Long",
                                                80
                                            ]
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"order_no",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"c8f8f9d9-c130-4efd-bc8d-c41b092d607c"
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"price",
                                            "keyType":"NULL",
                                            "type":3,
                                            "value":[
                                                "java.math.BigDecimal",
                                                10
                                            ]
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"sku_code",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"00001"
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"sku_name",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"肘子"
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"total",
                                            "keyType":"NULL",
                                            "type":3,
                                            "value":[
                                                "java.math.BigDecimal",
                                                10
                                            ]
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"user_id",
                                            "keyType":"NULL",
                                            "type":-5,
                                            "value":[
                                                "java.lang.Long",
                                                1
                                            ]
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                }
            }
        ]
    ]
}

beforeImage 和 afterImage 保存了操作前和操作后数据用于发生异常数据回滚,

第一阶段执行流程图如下

  1. 提交前,先TC注册分支事务,申请 更新表数据的中主键值的全局锁
  2. 本地事务提交,把生成的Undo_log一并提交
  3. 将本地事务执行结果上报给TC

在这里可以看出,AT模式与XA模式最大的不同点,本地事务执行完成立刻提交马上释放资源,根据保存到数据库中undo_log进行异常补偿操作。AT模式实际上降低了锁的范围,从而提升了分布式事务的处理效率。

在这里插入图片描述

第二阶段

TC接收到了所有分支事务的执行结果,决定对全局事务进行提交或者回滚操作

事务提交

如果决定全局提交,说明此时所有分支事务都已经完成了提交,所以只需要清除undo_log就可以了。

  1. 分支事务收到TC的提交请求后把请求放到一个异步队列中,并马上返回提交成功的结果
  2. 从异步队列中执行分支提交亲贵,批量删除undo_log
    在这里插入图片描述

事务回滚

如果决定回滚,则根据undo_log中的记录进行补偿,如果全局事务回滚成功,数据一致性就得到了保证

  1. 根据XID和 branchID查找到对应的undo_log
  2. 将undo_log中的 afterImage镜像数据与当前业务表中的数据进行比较,如果不同,说明数据被当前全局事务之外的动作做了修改,那么事务将不会回滚
  3. 如果 afterImage中的数据和当前业务表中的数据相同,则根据undo_log中的beforeImage镜像数据生成回滚语句并执行
  4. 提交本地事务,并把本地事务的执行结果上报给TC
    在这里插入图片描述

AT 模式数据隔离性保证

在AT模式中,当多个全局事务操作同一张表时,他的事务隔离型保证时通过全局锁来实现的。

写隔离

  • 一阶段本地事务提交前,需要确保先拿到 全局锁 。
  • 拿不到 全局锁 ,不能提交本地事务。
  • 拿 全局锁 的尝试被限制在一定范围内,超出时间范围将放弃,并回滚本地事务,释放本地锁。

我们假设
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

  1. tx1 先执行,开启本地事务,拿到本地锁,更新数据库 m = 1000 - 100
  2. tx1 提交前, 先拿到该记录的全局锁,本地提交释放本地锁。
  3. tx2 执行, 开始本地事务,拿到本地锁,更新 m = 900 - 100
  4. tx2 提交前,尝试拿到该记录的全局锁。此时等待 tx1 释放全局锁
  5. tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
    在这里插入图片描述
    如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
    此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

在这里插入图片描述

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1999

每人一点点,明天会更好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值