文章目录
为什么需要Seata?
如图:
微服务下订单、购物车、商品都在三个不同的服务中,每个微服务都有独立的数据库。
而这三个业务是相关联的,必须保证同时成功或同时失败,否则就会造成数据的不一致;
这样就违背了ACID原则(原子性、一致性、隔离性、持久性)
若是单体项目
,则MySQL的单体事务(InnoDB引擎)就可以满足;
但在微服务
下,如何跨越多个服务管理事务、数据库呢?Seata
就发挥作用了!
认识Seata
Seata:一款开源的分布式解决方案,提供高性能和简单易用的分布式事务服务。
分布式事务产生的原因:参与事务的多个分支事务相互之间无感知,不知道彼此的状态,这时候需要一个中间人来协调,这个中间人就称为"事务协调者"。
因此Seata的事务管理中,存在3个重要的角色:
TC (Transaction Coordinator) - 事务协调者
:维护全局和分支事务的状态,协调全局事务提交或回滚。TM (Transaction Manager) - 事务管理器
:定义全局事务的范围、开始全局事务、提交或回滚全局事务。RM (Resource Manager) - 资源管理器
:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TM 和 RM 可以理解为 Seata 的客户端部分,引入到参与事务的微服务依赖中即可。
将来 TM 和 RM 就会协助微服务,实现本地分支事务与 TC 之间交互,实现事务的提交或回滚。
而 TC 服务则是事务协调中心,是一个独立的微服务,需要单独部署。
Seata 支持四种不同的分布式事务解决方案:XA、TCC、AT、SAGA
快速部署
1、部署TC
选择MySQL存储Seata数据是为了持久化
创建数据库:
执行SQL创建数据库+表
CREATE DATABASE IF NOT EXISTS `seata`;
USE `seata`;
CREATE TABLE IF NOT EXISTS `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_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
Docker 部署:
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.150.101 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2
(注意IP要设置成公网IP)
Seata的文件可以去
黑马微服务B站课程
去下载,文件不方便上传优快云
注意:要修改 application.yml的 naocs、mysql 地址信息
启动Seata控制台:
控制台地址,账号密码默认:admin
http://localhost:7099
(注意要开放7099、8099端口)
2、部署客户端(TM、RM)
引入依赖
引入Seata依赖到pom.xml
Seata依赖于Nacos,因此Nacos也不可少
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
共享配置
在nacos上添加seata配置
然后,在涉及到分布式事务的服务的boostrap.yaml
中添加共享seata配置
spring:
application:
name: item-service # 微服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml
shared-configs:
- data-id: shared-jdbc.yaml
- data-id: shared-log.yaml
- data-id: shared-swagger.yaml
- data-id: shared-seata.yaml
添加数据表
在hm-trade、hm-cart、hm-item
这三个数据库中执行SQL
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
将本地事务改为分布式事务
trade-service 模块下的 com.hmall.trade.service.impl.OrderServiceImpl 类中的 createOrder 方法,也就是下单业务方法。
将其上的@Transactional
注解改为 Seata 提供的@GlobalTransactional
:
@GlobalTransactional
注解就是在标记事务的起点,将来 TM 就会基于这个方法判断全局事务范围,初始化全局事务。
同时,被远程调用的几个方法也可以加上@Transactional
事务注解。
(需要改为@GlobalTransactional)
(需要改为@GlobalTransactional)
重启三个服务后,分布式事务就成功实现了
本项目学习源码源于黑马微服务B站课程