Seata使用AT模式及源码解析(一)

一.分布式事务问题

在一系列微服务系统当中,假如不存在分布式事务,会发生什么呢?

正常情况下页面发起一个请求假设流程中对服务A操作的同时还需要调用服务B,服务C进行操做那么这里对于服务A及服务B的操作都是在自己的服务中进行的那么事务也就是在自己的服务中控制的一切都没问题的时候,服务A,B,C完成自己本地的事务操作后提交数据没有问题

异常情况 

假设子流程服务B出现事务回滚而主流程及子流程服务C均已提交事务那么就出现事务不一致问题了,数据就发生错误

解决方案:

  • 两阶段提交方案/XA方案
  • TCC Try、Confirm、Cancel方案
  • 本地消息表
  • 可靠消息最终一致性方案
  • 最大努力通知方案
  • ..............

以上方案都需要自己结合实际业务配合中间件写回滚逻辑需要有丰富的经验一般公司可以使用阿里提供的Seata框架解决

二.Seata简介

官网https://seata.io/zh-cn/index.html

GitHub:https://github.com/seata/seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。

术语:

  • 全局事务IDTransaction ID XID):同一事务管理即为一个ID
  • 事务协调者TC (Transaction Coordinator) 维护全局和分支事务的状态,驱动全局事务提交或回滚。seata服务
  • 事务管理器TM (Transaction Manager) 定义全局事务的范围:开始全局事务、提交或回滚全局事务。@GlobalTransactional注解事务发起方
  • 资源管理器RM (Resource Manager) 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。就是各个服务连接的数据库

执行流程

  • TM向TC申请开启一个全局事务全局事务创建成功并生成XID
  • XID在服务间调用传播
  • RM向TM注册分支事务将其交给XID对应的全局事务管理
  • TM向TC发起针对XID全局事务的回滚或提交
  • TC调起XID对应的全部分支事务回滚或提交

三.初步使用

1.下载nacos:https://github.com/alibaba/nacos/releases/tag/1.3.2

2.下载Seatahttps://github.com/seata/seata/releases/tag/v1.3.0

3.源码下载:https://github.com/seata/seata或者https://gitee.com/seata-io/seata

4.解压启动nacos:sh bin/startup.sh -m standalone

浏览器访问127.0.0.1:8848/nacos

5.修改seata/conf/registry.conf 文件

6.拉取seata源代码修改源代码

7.将seata配置提交到nacos保存:sh nacos-config.sh 127.0.0.1

执行源代码的nacos-config.sh脚本提示Init nacos config finished, please start seata-server.表示成功

8.创建seata库并在库中建表seata 0.9.0之前会提供sql脚本之后版本不提供

-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `global_table` (
  `xid` varchar(128)  not null,
  `transaction_id` bigint,
  `status` tinyint not null,
  `application_id` varchar(32),
  `transaction_service_group` varchar(32),
  `transaction_name` varchar(128),
  `timeout` int,
  `begin_time` bigint,
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`xid`),
  key `idx_gmt_modified_status` (`gmt_modified`, `status`),
  key `idx_transaction_id` (`transaction_id`)
);

-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
  `branch_id` bigint not null,
  `xid` varchar(128) not null,
  `transaction_id` bigint ,
  `resource_group_id` varchar(32),
  `resource_id` varchar(256) ,
  `lock_key` varchar(128) ,
  `branch_type` varchar(8) ,
  `status` tinyint,
  `client_id` varchar(64),
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`branch_id`),
  key `idx_xid` (`xid`)
);

-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
  `row_key` varchar(128) not null,
  `xid` varchar(96),
  `transaction_id` long ,
  `branch_id` long,
  `resource_id` varchar(256) ,
  `table_name` varchar(32) ,
  `pk` varchar(36) ,
  `gmt_create` datetime ,
  `gmt_modified` datetime,
  primary key(`row_key`)
);

9.启动seata server:./bin/seata-server.sh

10.业务数据库准备以官网订单业务逻辑为案例)订单->库存->账户,创建三个数据库seata_order,seata_account,seata_storage

分别在三个库下执行对应建表语句

DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

三个库都执行以下建表语句日志记录表

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

11.导入项目seata_test:https://github.com/pengdakun/seata_test.git

注意事项:application.yml配置

全局事务注解:

 

12.启动三个服务请求接口http://localhost:2001/order/create,需要在库存,账户表各加一条数据

{
"userId":1,
"commodityCode":1,
"count":2,
"money":10
}

四.原理解析

1.在看TC/TM/RM执行流程

  • TM开启分布式事务TM向TC注册全局事务
  • RM向TC汇报准备情况
  • TM结束分布式事务事务一阶段结束TM通知TC提交或回滚事务
  • TC汇总事务信息决定回滚或提交分布式事务
  • TC通知所有RM提交或回滚事务二阶段结束

2.一阶段:seata会拦截业务SQL

  • 解析SQL语句找到业务SQL要更新的业务数据在业务数据要被更新前保存为before image
  •  执行业务SQL语句在业务数据更新之后保存after image并生成行锁

3.二阶段提交数据:如果一阶段一切正常二阶段提交只需要删除一阶段创建的before imageafter image行锁即可

4.二阶段回滚数据:seata需要回滚一阶段已经执行的SQL数据主要是使用before image进行数据还原但是在还原之前需要将当前数据与after image进行对比如果数据一致则直接还原否则需要人工处理之后删除一阶段创建的before imageafter image行锁

5.Debug查看表数据:

seata库:

order库:通过select CONVERT(rollback_info USING utf8mb4) AS body from undo_log;查询rollback_info

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

                        ]
                    ]
                },
                "afterImage":{
                    "@class":"io.seata.rm.datasource.sql.struct.TableRecords",
                    "tableName":"order_tbl",
                    "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":4,
                                            "value":19
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"user_id",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"1"
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"commodity_code",
                                            "keyType":"NULL",
                                            "type":12,
                                            "value":"1"
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"count",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":2
                                        },
                                        {
                                            "@class":"io.seata.rm.datasource.sql.struct.Field",
                                            "name":"money",
                                            "keyType":"NULL",
                                            "type":4,
                                            "value":10
                                        }
                                    ]
                                ]
                            }
                        ]
                    ]
                }
            }
        ]
    ]
}

JSON中有beforeImage用于保存更新前的数据(库存,账户中undo_log均有记录),通过befoae_image和after_image全局事务的提交与回滚就是通过befoae_image和after_image进行事务补偿的之后在删除全部非业务表数据。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值