16. SpringCloud Alibaba Seata处理分布式事务
16.1 分布式事务问题
分布式前:
单机单库没这个分布式事务问题,但是随着项目的业务不断增多,微服务与数据库的关系从1:1 -> 1:N -> N:N
分布式后:
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,
业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
16.2 Seata简介
16.2.1 是什么
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址:http://seata.io/zh-cn/
16.2.2 能干嘛
一个典型的分布式事务过程
1.分布式事务处理过程的一ID+三组件模型
Transaction ID XID: 全局唯一的事务ID
Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager ™: 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
2.处理过程
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
XID 在微服务调用链路的上下文中传播;
RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
TM 向 TC 发起针对 XID 的全局提交或回滚决议;
TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
16.2.3 去哪下
发布说明: https://github.com/seata/seata/releases
16.2.4 怎么玩
单机版使用==@Transactional==,分布式事务使用==@GlobalTransactional==
SEATA 的分布式交易解决方案
16.3 Seata-Server安装
16.3.1 官网地址
16.3.2 下载版本
https://github.com/seata/seata/releases ,本次学习使用的是seata-server-0.9.0.zip
16.3.3 seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件
- 先备份原始file.conf文件
- 主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
file.conf 主要修改service模块和store模块
service { ###################给事务组起一个名字(啥都行,但是后期要在项目中进行对应) vgroup_mapping.my_test_tx_group = "fsp_tx_group" default.grouplist = "127.0.0.1:8091" enableDegrade = false disable = false max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } ## transaction log store store { ## store mode: file、db #############修改存储模式为数据库 mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } ## database store db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" ###########修改自己数据库的相关信息 driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" user = "root" password = "root" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } }
16.3.4 mysql5.7数据库新建库seata
先建立数据库seata.建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面
16.3.5 修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件
目的是:指明注册中心为nacos,及修改nacos连接信息
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" }
16.3.6 启动seata-server
16.4 订单/库存/账户业务数据库准备
以下演示都需要先启动Nacos后启动Seata,保证两个都OK. Seata没启动报错no available server to connect
16.4.1 分布式事务业务说明
主要业务:下订单—>扣库存—>减账户(余额)
这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
16.4.2 创建业务数据库
- seata_order:存储订单的数据库;
- seata_storage:存储库存的数据库;
- seata_account:存储账户信息的数据库。
- 建库SQL
CREATE DATABASE seata_order; CREATE DATABASE seata_storage; CREATE DATABASE seata_account;
16.4.3 按照上述3库分别建对应业务表
- seata_order库下建t_order表
- seata_storage库下建t_storage 表
- seata_account库下建t_account 表
- 建表Sql
###########################t_order################################################ CREATE TABLE t_order ( `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id', `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id', `count` INT(11) DEFAULT NULL COMMENT '数量', `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额', `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; SELECT * FROM t_order; ###########################t_storage############################################# CREATE TABLE t_storage ( `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id', `total` INT(11) DEFAULT NULL COMMENT '总库存', `used` INT(11) DEFAULT NULL COMMENT '已用库存', `residue` INT(11) DEFAULT NULL COMMENT '剩余库存' ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100'); SELECT * FROM t_storage; ###########################t_account############################################### CREATE TABLE t_account ( `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id', `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id', `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度', `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额', `residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度' ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000'); SELECT * FROM t_account;
16.4.4 按照上述3库分别建对应的回滚日志表
- 订单-库存-账户3个库下都需要建各自的回滚日志表
- \seata-server-0.9.0\seata\conf目录下的db_undo_log.sql
-- the table to store seata xid data -- 0.7.0+ add context -- you must to init this sql for you business databese. the seata server not need it. -- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库) -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log DROP TABLE `undo_log`; 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;
16.4.5 最终效果