SpringCloud系列之集成分布式事务Seata应用篇
前言
单体应用被拆分成各个独立的业务模块后,就不得不要去面对分布式事务,好在阿里已经开源分布式事务组件Seata,虽还在迭代中,难免会有bug产生,但随着社区发展及反馈,相信终究会越来越稳定,话不多说让我们开始吧。
项目版本
spring-boot.version:2.2.5.RELEASE
spring-cloud.version:Hoxton.SR3
seata.version:1.2.0
项目说明
项目模块说明如下:
前端请求接口经由网关服务进行路由转发后进入cloud-web模块,经cloud-web模块调用相应业务微服务模块,执行业务逻辑后响应前端请求。
Seata服务端部署
1.下载Seata服务端部署文件
https://github.com/seata/seata/releases/download/v1.2.0/seata-server-1.2.0.zip
如嫌下载慢,可关注本文下方微信公众号二维码,关注后回复“666”即可获取开发常用工具包
2.解压至本地目录后,执行seata-server.bat脚本,过程中无报错则说明部署正常,Linux环境下操作类似不做展开说明
Seata客户端集成
cloud-web
部分pom.xml,后续模块引入seata依赖一样,后续模块不再单独说明
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.2.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
application.properties ,后续模块引入seata配置项大致一样,根据业务模块调整3个配置项即可,后续模块不再单独说明
# seata配置
seata.enabled=true
#seata.excludes-for-auto-proxying=firstClassNameForExclude,secondClassNameForExclude
seata.application-id=cloud-web
seata.tx-service-group=cloud-web_tx_group
seata.enable-auto-data-source-proxy=true
seata.use-jdk-proxy=false
seata.client.rm.async-commit-buffer-limit=1000
seata.client.rm.report-retry-count=5
seata.client.rm.table-meta-check-enable=false
seata.client.rm.report-success-enable=false
seata.client.rm.saga-branch-register-enable=false
seata.client.rm.lock.retry-interval=10
seata.client.rm.lock.retry-times=30
seata.client.rm.lock.retry-policy-branch-rollback-on-conflict=true
seata.client.tm.commit-retry-count=5
seata.client.tm.rollback-retry-count=5
seata.client.tm.degrade-check=false
seata.client.tm.degrade-check-allow-times=10
seata.client.tm.degrade-check-period=2000
seata.client.undo.data-validation=true
seata.client.undo.log-serialization=jackson
seata.client.undo.only-care-update-columns=true
seata.client.undo.log-table=undo_log
seata.client.log.exceptionRate=100
seata.service.vgroup-mapping.cloud-web_tx_group=default
seata.service.grouplist.default=${cloud-web.seata.service.grouplist.default}
seata.service.enable-degrade=false
seata.service.disable-global-transaction=false
seata.transport.shutdown.wait=3
seata.transport.thread-factory.boss-thread-prefix=NettyBoss
seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
seata.transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
seata.transport.thread-factory.share-boss-worker=false
seata.transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
seata.transport.thread-factory.client-selector-thread-size=1
seata.transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
seata.transport.thread-factory.worker-thread-size=default
seata.transport.thread-factory.boss-thread-size=1
seata.transport.type=TCP
seata.transport.server=NIO
seata.transport.heartbeat=true
seata.transport.serialization=seata
seata.transport.compressor=none
seata.transport.enable-client-batch-send-request=true
seata.config.type=file
seata.registry.type=file
重点3个配置项需要调整下,其余保持默认
seata.application-id=cloud-web
seata.tx-service-group=cloud-web_tx_group
seata.service.vgroup-mapping.cloud-web_tx_group=default
OrderController.java
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
OrderFacade orderFacade;
@GlobalTransactional
@GetMapping("/add")
public String add(@RequestParam("cartId") Long cartId){
orderFacade.addOrder(cartId);
return "OK";
}
}
在需要开启分布式事务的方法上添加@GlobalTransactional注解即可
module-order
项目结构图如下
OrderService.java
@RestController
public class OrderService implements OrderFacade {
@Autowired
private TbOrderMapper tbOrderMapper;
@Autowired
private CartFacade cartFacade;
@Autowired
private GoodsFacade goodsFacade;
@Autowired
private WalletFacade walletFacade;
/**
* <p >
* 功能:新增订单
* </p>
* @param cartId 购物车ID
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public void addOrder(Long cartId) {
CartDTO cart = cartFacade.getCartById(cartId);
TbOrder order = new TbOrder();
order.setUserId(cart.getUserId());
order.setGoodsId(cart.getGoodsId());
order.setOrderNo(String.valueOf(System.currentTimeMillis()));
order.setCreateTime(System.currentTimeMillis());
order.setUpdateTime(order.getCreateTime());
order.setIsDeleted(Byte.valueOf("0"));
// 新增订单
tbOrderMapper.insert(order);
// 删除购物车
cartFacade.deleteCartById(cartId);
GoodsDTO goods = goodsFacade.getByGoodsId(cart.getGoodsId());
// 扣减库存
goodsFacade.substractStock(goods.getId());
// 扣减金额
walletFacade.substractMoney(cart.getUserId(),goods.getMoney());
throw new RuntimeException();
}
module-cart
项目结构图如下
CartService.java
@RestController
public class CartService implements CartFacade {
Logger LOGGER = LoggerFactory.getLogger(CartService.class);
@Autowired
private TbCartMapper tbCartMapper;
/**
* <p >
* 功能:增加商品至购物车
* </p>
* @param userId 用户ID
* @param goodsId 商品ID
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public String addCart(Long userId,Long goodsId) {
TbCart cart = new TbCart();
cart.setUserId(userId);
cart.setGoodsId(goodsId);
cart.setCreateTime(System.currentTimeMillis());
cart.setUpdateTime(cart.getCreateTime());
cart.setIsDeleted(Byte.valueOf("0"));
tbCartMapper.insert(cart);
return null;
}
/**
* <p >
* 功能:获取购物车信息
* </p>
* @param cartId 购物车ID
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public CartDTO getCartById(Long cartId) {
CartDTO cartDTO = null;
TbCart cart = tbCartMapper.selectById(cartId);
if (null != cart) {
cartDTO = new CartDTO();
cartDTO.setUserId(cart.getUserId());
cartDTO.setGoodsId(cart.getGoodsId());
}
return cartDTO;
}
/**
* <p >
* 功能:删除购物车信息
* </p>
* @param cartId 购物车ID
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public void deleteCartById(Long cartId) {
tbCartMapper.deleteById(cartId);
}
}
module-goods
项目结构图如下
GoodsService.java
@RestController
public class GoodsService implements GoodsFacade {
@Autowired
private TbGoodsMapper tbGoodsMapper;
/**
* <p >
* 功能:获取商品信息
* </p>
* @param goodsId 商品ID
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public GoodsDTO getByGoodsId(Long goodsId) {
GoodsDTO goodsDTO = null;
TbGoods goods = tbGoodsMapper.selectById(goodsId);
if (null != goods) {
goodsDTO = new GoodsDTO();
BeanUtils.copyProperties(goods,goodsDTO);
}
return goodsDTO;
}
/**
* <p >
* 功能:扣减商品库存
* </p>
* @param goodsId 商品ID
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public void substractStock(@RequestParam("goodsId") Long goodsId) {
if (tbGoodsMapper.updateSubstractStockNumById(goodsId) != 1) {
throw new RuntimeException("扣减库存异常");
}
}
}
module-wallet
项目结构图如下
WalletService.java
@RestController
public class WalletService implements WalletFacade {
@Autowired
private TbWalletMapper tbWalletMapper;
/**
* <p >
* 功能:扣减用户钱包金额
* </p>
* @param userId 用户ID
* @param money 金额
* @author wuyubin
* @date 2020年05月22日
* @return
*/
@Override
public void substractMoney(Long userId, BigDecimal money) {
if (tbWalletMapper.updateSubstractMoney(userId,money) != 1) {
throw new RuntimeException("用户金额异常");
}
}
}
表结构说明
undo_log 表:seata依赖表
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 DEFAULT CHARSET=utf8;
tb_cart 表:购物车表
CREATE TABLE `tb_cart` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
`create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
`update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
`is_deleted` tinyint(4) NULL DEFAULT 0 COMMENT '删除标志 0:未删除;1:已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '购物车表' ROW_FORMAT = Dynamic;
-- 初始化数据
INSERT INTO `tb_cart` VALUES (1, 1, 1, 1590114829756, 1590114829756, 0);
tb_goods 表:商品表
CREATE TABLE `tb_goods` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
`stock_num` bigint(20) NULL DEFAULT NULL COMMENT '商品库存数量',
`money` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品金额',
`create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
`update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
`is_deleted` tinyint(4) NULL DEFAULT NULL COMMENT '删除标志 0:未删除;1:已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商品表' ROW_FORMAT = Dynamic;
-- 初始化数据
INSERT INTO `tb_goods` VALUES (1, '键盘', 100, 100.00, 1590132270000, 1590377130, 0);
tb_wallet 表:钱包表
CREATE TABLE `tb_wallet` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`money` decimal(10, 2) NULL DEFAULT NULL COMMENT '金额',
`create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间戳',
`update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间戳',
`is_deleted` tinyint(4) NULL DEFAULT NULL COMMENT '删除标志 0:未删除;1:已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '钱包表' ROW_FORMAT = Dynamic;
-- 初始化数据
INSERT INTO `tb_wallet` VALUES (1, 1, 500.00, 1590132270000, 1590377130, 0);
tb_order 表:订单表
CREATE TABLE `tb_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单编号',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
`goods_id` bigint(20) NULL DEFAULT NULL COMMENT '商品ID',
`create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间',
`update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint(4) NULL DEFAULT 0 COMMENT '是否删除 0:未删除;1:已删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;
所有服务启动后,请求以下接口
http://localhost:9005/cloud-web/order/add?cartId=1
查看各服务模块日志,你会发现均有如下信息输出,提示已回滚
因为在order模块中addOrder方法下,我这边人为抛出一个运行时异常,看样子事务已经生效了
我们看下数据库中数据是否已回滚正常,核实后发现数据均以回滚
接下来我们把order模块中addOrder方法下将“throw new RuntimeException();”代码块注释掉,重启订单模块服务后再次访问上述接口地址,发现访问正常
查看各服务模块日志,你会发现均有如下信息输出,提示已提交成功
我们再一次核实下数据表中的数据
购物车表已将原先记录逻辑删除
订单表新增一条订单记录
商品表库存数量已扣减1
钱包表金额已扣减100
最后我们测试其中一个服务出现异常,验证下事务是否回滚正常,我们将购物车表逻辑删除恢复正常,将商品表库存改成0,这时我们再请求上述接口地址,发现返回异常了,我们再核实下数据,发现数据表中的数据均以回滚。好啦,SpringCloud集成分布式事务Seata的示例就到这里啦,后续有深入的研究再分享出来。
参考资料
https://github.com/seata/seata
https://seata.io/zh-cn/docs/overview/what-is-seata.html
系列文章
SpringCloud系列之配置中心(Config)使用说明
SpringCloud系列之服务注册发现(Eureka)应用篇
SpringCloud系列之集成分布式事务Seata应用篇的更多相关文章
- 阿里分布式事务seata入门(采坑)
1. 阿里分布式事务seata入门(采坑) 1.1. 前言 seata是feascar改名而来,这是阿里在19年年初开源出来的分布式事务框架,当初刚出来的时候就想研究下了,一直拖到了现在,目前是0.8 ...
- 分布式事务(Seata)原理 详解篇,建议收藏
前言 在之前的系列中,我们讲解了关于Seata基本介绍和实际应用,今天带来的这篇,就给大家分析一下Seata的源码是如何一步一步实现的.读源码的时候我们需要俯瞰起全貌,不要去扣一个一个的细节,这样我们 ...
- SpringCloud系列之集成Dubbo应用篇
目录 前言 项目版本 项目说明 集成Dubbo 2.6.x 新项目模块 老项目模块 集成Dubbo 2.7.x 新项目模块 老项目模块 参考资料 系列文章 前言 SpringCloud系列开篇文章就说 ...
- 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾
https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...
- SpringBoot系列之集成logback实现日志打印(篇二)
SpringBoot系列之集成logback实现日志打印(篇二) 基于上篇博客SpringBoot系列之集成logback实现日志打印(篇一)之后,再写一篇博客进行补充 logback是一款开源的日志 ...
- SpringCloud整合分布式事务Seata 1.4.1 支持微服务全局异常拦截
项目依赖 SpringBoot 2.5.5 SpringCloud 2020.0.4 Alibaba Spring Cloud 2021.1 Mybatis Plus 3.4.0 Seata 1.4. ...
- 出席分布式事务Seata 1.0.0 GA典礼
前言 图中那个红衣服的就是本人 什么是分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上. 简单的说,就是一次大的操作由不同的小 ...
- spring boot:使用分布式事务seata(druid 1.1.23 / seata 1.3.0 / mybatis / spring boot 2.3.2)
一,什么是seata? Seata:Simpe Extensible Autonomous Transcaction Architecture, 是阿里中间件开源的分布式事务解决方案. 前身是阿里的F ...
- 微服务开发的最大痛点-分布式事务SEATA入门简介
前言 在微服务开发中,存在诸多的开发痛点,例如分布式事务.全链路跟踪.限流降级和服务平滑上下线等.而在这其中,分布式事务是最让开发者头痛的.那分布式事务是什么呢? 分布式事务就是指事务的参与者.支持事 ...
随机推荐
- java实现第六届蓝桥杯垒骰子
垒骰子 题目描述 赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体. 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰 ...
- 温故知新-多线程-forkjoin、CountDownLatch、CyclicBarrier、Semaphore用法
Posted by 微博@Yangsc_o 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0 文章目录 摘要 forkjoin C ...
- Python中map和reduce函数
①从参数方面来讲: map()函数: map()包含两个参数,第一个是参数是一个函数,第二个是序列(列表或元组).其中,函数(即map的第一个参数位置的函数)可以接收一个或多个参数. reduce() ...
- 使用jdk1.8 stream特性对参数名称进行排序
在对外对接的时候,通常会碰到签名方式, 然后签名的时候,要求按照参数名称进行排序. 比如参数为 c=22&a=1, 需要将结果排序为a=1&c=22, 然后再进行别的运算. 可以使用j ...
- 基于使用ISCSI存储的ibmmq通过heartbeat实现HA方案以及碰到的问题总结
一.背景 ibmmq是一种传统架构的mq产品,运行稳定,有其自身优点,但在高可用(HA)这一块需要使用公司根据自身需求选用高可用(HA)产品,但由于市面HA商业产品较贵,所以使用linux操作系统级的 ...
- 64位手机无法加载x5(libmttwebview.so is 32-bit instead of 64-bit)
x5内核暂时不提供64位的so文件,在64位手机上需要让AP以32位模式运行. 具体操作如下: 1.如果使用是Eclipse则需要将所有的.so文件都放置在so加载目录:lib/armeabi文件夹下 ...
- 为什么要使用Mybatis-现有持久化技术的对比
1)JDBC SQL 夹在Java代码块里,耦合度高导致硬编码内伤 维护不易且实际开发需求中SQL有变化,频繁修改的情况很多 2)Hibernate 和 JPA 长难复杂SQL, 对于Hibernat ...
- 安装 zabbix
目标在centos8下安装部署zabbix 4.4 zabbix介绍zabbix由2部分构成,zabbix server与可选组件zabbix agent.zabbix server可以通过SNMP, ...
- 完美解决PYQT5登录界面跳转主界面方法
该问题,有很多种方法,但是很多方法要么这个有问题,要么那个有问题,最后终于找到一种没问题的方法.记录一下: Login.py(登录窗口)文件 import sys from PyQt5 import ...
- spark源码解析大全
第1章 Spark 整体概述 1.1 整体概念 Apache Spark 是一个开源的通用集群计算系统,它提供了 High-level 编程 API,支持 Scala.Java 和 Pytho ...