Seata 分布式事务 快速开始
一、场景分析
假设现在有这么两个微服务 order、product,order 通过 feign 调用 product。
1.1、表结构
CREATE DATABASE IF NOT EXISTS `oms`;CREATE TABLE `oms_order` (`order_id` int NOT NULL AUTO_INCREMENT COMMENT '订单ID',`product_id` int NOT NULL COMMENT '商品ID',`quantity` int NOT NULL COMMENT '总数量',PRIMARY KEY (`order_id`)
) COMMENT='订单表';CREATE DATABASE IF NOT EXISTS `pms`;CREATE TABLE `pms_product` (`product_id` int NOT NULL AUTO_INCREMENT COMMENT '商品ID',`stock` int NOT NULL COMMENT '商品库存',`product_name` varchar(50) NOT NULL COMMENT '商品名称',PRIMARY KEY (`product_id`)
) COMMENT='商品表';INSERT INTO `pms_product` (`product_id`, `stock`, `product_name`)
VALUES (1, 100, 'HuaWei Mate60 Pro');
1.2、本地事务
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final ProductFeignService productFeignService;@Override@Transactional(rollbackFor = Exception.class)public Order createOrder() {// 1、扣减库存PmsDeductStockDto dto = new PmsDeductStockDto();dto.setNum(1);dto.setProductId(1);productFeignService.deductStock(dto);// 2、创建订单Order order = new Order();order.setQuantity(1);order.setProductId(1);this.baseMapper.insert(order);int i = 1/0;// 报错return order;}
}
OrderServiceImpl#createOrder 加上了 @Transactional 注解,它是一个 Spring AOP 事务方法。因为 / by zero 错误,事务回滚,oms 数据库并没有插入订单数据;但是 pms 却扣减库存成功了。
pms_product 的库存 stock 从 100 被扣减到 99。
数据出现不一致:没有订单,库存却凭空减少。
这种不同数据库,或者相同数据库但不同服务之间的事务操作,所造成的数据不一致现象,叫做分布式事务问题。
我们使用 Seata 看看怎么来解决这个问题 →→
二、角色
Seata 的三大角色:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
三、部署 Seata 服务端
3.1、版本选择
根据 Spring Cloud Alibaba 组件版本关系,到官网下载对应的 Seata 版本。
因为我的 Spring Cloud Alibaba 版本是 2021.0.5.0,所以下载 1.6.1.zip。
3.2、执行建表语句
创建数据库 seata:
CREATE DATABASE IF NOT EXISTS `seata`;
根据自己的数据库,执行下载包 seata\script\server\db 下脚本:
3.3、修改配置
- 修改 application.yml
修改 seata\conf\application.yml 文件,添加配置中心与注册中心信息。
seata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: 127.0.0.1:8848group: SEATA_GROUPdata-id: seataServer.propertiesusername: nacospassword: nacosregistry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUPcluster: defaultusername: nacospassword: nacos
Seata 作为分布式事务协调者,可以通过注册中心与微服务通讯。
存疑:
config.nacos.namespace 如果配置 public 会起不来,不知道其他人会不会。
- 修改 config.txt
修改 seata\script\config-center\config.txt 文件,并将内容上传到 Nacos 配置中心 public 命名空间下,data-id 为 seataServer.properties。
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.40.111:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
修改事务信息的存储方式为 db,并修改数据库连接方式。
注意 mysql 8.0 以上,使用 com.mysql.cj.jdbc.Driver 驱动。
3.4、启动
运行 seata\bin\seata-server.bat
Nacos 服务列表有 seata-server 证明启动成功。
登录 http://localhost:7091/#/login 用户名密码 seata/seata
四、配置 Seata 客户端(AT 模式)
4.1、导入依赖
order、product 的 pom.xml 添加依赖:
<!-- seata -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
4.2、配置 application.yml
order、product 的 application.yml 添加配置:
seata:application-id: ${spring.application.name}tx-service-group: default_tx_groupregistry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUPusername: nacospassword: nacosconfig:type: nacosnacos:server-addr: 127.0.0.1:8848group: SEATA_GROUPdata-id: seataServer.propertiesusername: nacospassword: nacos
- tx-service-group
seata 服务分组,要与服务端配置 service.vgroupMapping 的后缀对应。
- config.nacos.data-id
新版本的 seata 服务端、客户端配置可以共用同一套。
4.3、创建 undo_log 表(仅AT模式)
order、product 数据库创建 undo_log:
-- 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 = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
4.4、添加 @GlobalTransactional 注解
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final ProductFeignService productFeignService;@Override// @Transactional(rollbackFor = Exception.class)@GlobalTransactional(name="createOrder",rollbackFor=Exception.class)public Order createOrder() {// 1、扣减库存PmsDeductStockDto dto = new PmsDeductStockDto();dto.setNum(1);dto.setProductId(1);productFeignService.deductStock(dto);// 2、创建订单Order order = new Order();order.setQuantity(1);order.setProductId(1);this.baseMapper.insert(order);int i = 1/0;return order;}
}
4.5、启动并测试
访问 localhost:8012/order/create
order-service 控制台报错:
product-service 回滚了事务:
数据库:
在文章开头从 100 减为 99 后,就没有再变化了。证明 Seata 是有效的。