seata是阿里开源的一款分布式事务中间件,有关seata的详细介绍可以查看seata的官方文档:http://seata.io/zh-cn/docs/user/configurations.html
这里我们来用官方的demo来实现seata的分布式事务。我们在此采用的是AT事务模式。实现分布式事务首先我们的服务得是分布式的,这里我们用dubbo框架+nacos作为注册中心。
nacos注册中心的搭建大家可以参考官方文档:https://nacos.io/zh-cn/docs/what-is-nacos.html。搭建后nacos官方文档之后,接下来就要搭建seata服务了,分布式事务的核心就是这个seata服务,相关搭建可以参考官方文档:http://seata.io/zh-cn/docs/user/quickstart.html。搭建seata服务首先要在数据库中创建undo_log表。seata的配置文件中配置好nacos的注册中心的地址以及mysql数据库的配置。
undo_log建表语句:
-- 注意此处0.3.0+ 增加唯一索引 ux_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;
数据库的配置在conf目录下的file.conf,这里我们使用的是mysql数据库所以配置好mysql数据库的配置:
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&rewriteBatchedStatements=true"
user = ""
password = ""
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
nacos注册中心的配置在conf目录下的registry.conf配置文件中配置,这里我们使用的是nacos做为配置中心,所以配置好nacos。
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
搭建好相关服务之后,我们就来下载github上的官方demo:https://github.com/seata/seata-samples
我们使用的是springboot-dubbo-seata这个demo,其结构目录如下
--springboot-dubbo-seata
--samples-account
--samples-business
--samples-common
--samples-order
--samples-storage
使用主类使用@EnableTransactionManagement注解为服务开启分布式事务,业务逻辑中使用@GlobalTransactional声明方法启用分布式事务。
主要业务逻辑在samples-business中如下所示:
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
public ObjectResponse handleBusiness(BusinessDTO businessDTO) {
System.out.println("开始全局事务,XID = " + RootContext.getXID());
ObjectResponse<Object> objectResponse = new ObjectResponse<>();
//1、扣减库存
CommodityDTO commodityDTO = new CommodityDTO();
commodityDTO.setCommodityCode(businessDTO.getCommodityCode());
commodityDTO.setCount(businessDTO.getCount());
ObjectResponse storageResponse = storageDubboService.decreaseStorage(commodityDTO);
//2、创建订单
OrderDTO orderDTO = new OrderDTO();
orderDTO.setUserId(businessDTO.getUserId());
orderDTO.setCommodityCode(businessDTO.getCommodityCode());
orderDTO.setOrderCount(businessDTO.getCount());
orderDTO.setOrderAmount(businessDTO.getAmount());
ObjectResponse<OrderDTO> response = orderDubboService.createOrder(orderDTO);
//打开注释测试事务发生异常后,全局回滚功能
flag = false;
if (!flag) {
throw new RuntimeException("测试抛异常后,分布式事务回滚!");
}
if (storageResponse.getStatus() != 200 || response.getStatus() != 200) {
throw new DefaultException(RspStatusEnum.FAIL);
}
objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode());
objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage());
objectResponse.setData(response.getData());
return objectResponse;
}
handleBusiness方法调用samples-storage服务的减库存方法decreaseStorage()和samples-order服务的新增订单方法createOrder()。此处代码中的如下是用来模拟事务回滚,抛出异常。如果将此代码注释,在减库存和增订单的操作会执行成功。
flag = false;
if (!flag) {
throw new RuntimeException("测试抛异常后,分布式事务回滚!");
}
业务代码mysql数据库脚本:
CREATE TABLE `t_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` varchar(32) DEFAULT NULL COMMENT '订单编号',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`commodity_code` varchar(32) DEFAULT NULL COMMENT '商品编号',
`count` int(11) DEFAULT '0' COMMENT '数量',
`amount` double DEFAULT '0.00' COMMENT '数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `t_storage` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '库存ID',
`commodity_code` varchar(32) DEFAULT NULL COMMENT '商品编号',
`name` varchar(32) DEFAULT NULL COMMENT '商品名称',
`count` int(11) DEFAULT '0' COMMENT '库存数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `t_account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账户ID',
`user_id` varchar(32) DEFAULT NULL COMMENT '用户ID',
`amount` double DEFAULT '0.00' COMMENT '数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
相关资料查阅:
http://seata.io/zh-cn/docs/user/quickstart.html
http://seata.io/zh-cn/docs/user/quickstart.html
https://mp.weixin.qq.com/s/2EeJU4569jOvyluRhrgdzQ