这是我参与更文挑战的第 21 天,活动详情查看: 更文挑战
Seata
一、概述
http://seata.io/zh-cn/docs/overview/what-is-seata.html
二、环境配置
下载安装
-
下载地址
https://github.com/seata/seata/releases/tag/v1.0.0
-
解压
-
修改
\seata\conf
下的file.conf
配置文件- service模块自定义事务名称
vgroup_mapping.my_test_tx_group = "fsp_tx_group"
- 事务日志的存储模式和数据库信息
service { #transaction service group mapping vgroup_mapping.my_test_tx_group = "fsp_tx_group" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #disable seata disableGlobalTransaction = false } store { ## store mode: file、db mode = "db" ## database store property 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://localhost:3306/seata" user = "root" password = "root" } }
- service模块自定义事务名称
-
建库建表,SQL在
seata-server-0.9.0\seata\conf
目录下 -
修改
registry.conf
配置文件registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { serverAddr = "localhost:8848" namespace = "" cluster = "default" } ... }
-
启动Nacos和Seata
三、SEATA 的分布式交易解决方案
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
数据库准备
CREATE DATABASE seata_order;
USE seata_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 CHARSET=utf8;
CREATE DATABASE seata_storage;
USE seata_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=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);
CREATE DATABASE seata_account;
USE seata_account;
CREATE TABLE t_account(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
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=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);
创建回滚日志表
SQL文件在\seata-server-0.9.0\seata\conf\db_undo_log.sql
四个数据库下都创建该表
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;
微服务准备
seata-order-service2001/seata-storage-service2002/seata-account-service2003
依赖
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
配置文件
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
# 自定义事务组名称,与配置中的对应
tx-service-group: fsp_tx_group
nacos:
discovery:
server-addr: 127.0.0.1:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.95.226.96:3306/seata?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: admin
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapper-locations: classpath:mapper/*.xml
注意:把之前配置的file.conf
和registry.conf
复制到resources
下
业务代码
测试
order模块发送GET请求http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
,通过Feign调用Account模块和storage模块做其相应的减账户余额和减库存操作。
存在的问题
现在在Account模块中的decrease
方法添加一个超时的操作
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("---------AccountServiceImpl扣减---------");
//制造超时异常
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
accountDao.decrease(userId,money);
}
继续上一步的请求,发现订单创建了并且库存也减了,但是账户余额没有变化,违背了数据的一致性,使用seata分布式事务解决。
解决方法
在业务的入口处加全局事务控制的注解@GlobalTransactional
@GlobalTransactional
public void createOrder(Order order) {
LOGGER.info("------新建订单-------");
orderDao.createOrder(order);
LOGGER.info("------Storage减库存-------");
storageService.decrease(order.getProductId(), order.getCount());
LOGGER.info("------Account扣减-------");
accountService.decrease(order.getUserId(), order.getMoney());
LOGGER.info("------修改订单状态-------");
orderDao.updateOrder(order.getUserId(), 0);
LOGGER.info("------end-------");
}
执行过程
默认的模式为AT模式。
分为两个阶段:
- 一阶段:解析SQL,根据SQL得到两个快照,一个前快照-保存执行SQL前的数据,一个后快照-保存执行SQL后的数据。
- 二阶段:
- 执行成功,删除快照
- 执行失败,会把后前快照重写会数据库,再删除快照。