springcloud集成seata实现分布式事务
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网:Apache Seata
文章目录
- 一、部署
- 1.下载
- 2.修改配置,nacos作注册中心,db存储
- 二、集成到springcloud项目
- 1.引入依赖
- 2.修改配置
- 3.新建数据表
- 4.编写代码
- 5.测试结果
一、部署
由于网络问题一直拉取docker镜像失败,所以这里采用了下载zip包直接部署的方式
版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub (需要和springcloud的版本对应)
1.下载
直接部署 | Apache Seata
上传服务器并解压

2.修改配置,nacos作注册中心,db存储
修改conf/application.yml
server:port: 7091spring:application:name: seata-serverlogging:config: classpath:logback-spring.xmlfile:path: ${user.home}/logs/seataextend:logstash-appender:destination: 192.168.100.52:4560kafka-appender:bootstrap-servers: 192.168.100.52:9092topic: logback_to_logstashconsole:user:username: seatapassword: seataseata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: 192.168.100.53:8848namespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466cgroup: spmp-systemusername: nacospassword: nacosdata-id: seataServer.propertiesregistry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacosnacos:application: seata-serverserver-addr: 192.168.100.53:8848group: spmp-systemnamespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466c# tc集群名称cluster: defaultusername: nacospassword: nacos
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'security:secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds: 1800000ignore:urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
此时启动seata服务端,已经可以在nacos服务列表看到seata-server服务
cd bin
sh seata-seaver.sh

然后在nacos新建配置文件seataServer.properties

store.mode=db
store.db.dbType=mysql
store.db.datasource=druid
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://192.168.100.52:3306/seata?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
store.db.user=seata
store.db.password=seata
这里注意先建数据库seata,然后执行建表sql,脚本在script/server/db/下的mysql.sql

然后重启seata服务端
可以从seata启动日志 logs/start.out 看到读取配置的相关信息

二、集成到springcloud项目
这里我们拿项目里其中两个微服务来测试,如图所示,服务1是被调用方,服务2是调用方

1.引入依赖
两个微服务的pom文件里都需要引入seata依赖
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.6.1</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><version>2021.0.5.0</version><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></exclusion></exclusions>
</dependency>
2.修改配置
修改两个微服务的配置文件,这里对应上前面seata服务端的配置
seata:registry:type: nacosnacos:application: seata-serverserver-addr: 192.168.100.53:8848group: spmp-systemnamespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466cusername: nacospassword: nacosconfig:type: nacosnacos:server-addr: 192.168.100.53:8848group: spmp-systemnamespace: 17a4ea5e-f549-4e4a-97a4-52ee2a9f466cdataId: seataServer.propertiesusername: nacospassword: nacostx-service-group: spmp-system
3.新建数据表
两个服务都需要新建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;
4.编写代码
-
修改全局异常处理器GlobalExceptionHandler
由于项目里的全局处理器通常都会将所有异常拦截,然后返回统一封装结果,而这会导致异常无法抛出
/*** 全局异常处理器** @author ruoyi*/ @RestControllerAdvice public class GlobalExceptionHandler {private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 先判断是否是seata全局事务异常,如果是,就直接抛给调用方,让调用方回滚事务* @param e* @throws Exception*/private void checkSeataError(Exception e) throws Exception {log.info("seata全局事务ID: {}", RootContext.getXID());// 如果是在一次全局事务里出异常了,就不要包装返回值,将异常抛给调用方,让调用方回滚事务if (StrUtil.isNotBlank(RootContext.getXID())) {throw e;}}/*** 请求方式不支持*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();log.error("请求地址'{}',不支持'{}'请求", requestUri, e.getMethod());return AjaxResult.error(e.getMessage());}/*** 业务异常*/@ExceptionHandler(ServiceException.class)public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) throws Exception {checkSeataError(e);log.error(e.getMessage(), e);Integer code = e.getCode();return StringUtils.isNotNull(code) ? AjaxResult.error(code, StrUtil.isEmpty(e.getMessage()) ? e.getCause().getMessage() : e.getMessage()) : AjaxResult.error(StrUtil.isEmpty(e.getMessage()) ? e.getCause().getMessage() : e.getMessage());}/*** 请求参数类型不匹配*/@ExceptionHandler(MethodArgumentTypeMismatchException.class)public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();String value = Convert.toStr(e.getValue());if (StringUtils.isNotEmpty(value)) {value = EscapeUtil.clean(value);}log.error("请求参数类型不匹配'{}',发生系统异常.", requestUri, e);return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value));}/*** 切面异常统一捕获*/@ExceptionHandler(AspectException.class)public ResponseResult<?> handleAspectException(AspectException aspectException) {aspectException.printStackTrace();return ResponseResult.error(aspectException.getResultStatus(), null);}/*** 系统基类异常捕获*/@ExceptionHandler(BasesException.class)public ResponseResult<?> handleBasesException(BasesException basesException) throws Exception {checkSeataError(basesException);basesException.printStackTrace();return ResponseResult.error(basesException.getResultStatus(), null);}/*** 拦截未知的运行时异常*/@ExceptionHandler(RuntimeException.class)public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();log.error("请求地址'{}',发生未知异常.", requestUri, e);return AjaxResult.error(e.getMessage());}/*** 系统异常*/@ExceptionHandler(Exception.class)public AjaxResult handleException(Exception e, HttpServletRequest request) throws Exception {checkSeataError(e);String requestUri = request.getRequestURI();log.error("请求地址'{}',发生系统异常.", requestUri, e);return AjaxResult.error(e.getMessage());}/*** 自定义验证异常*/@ExceptionHandler(BindException.class)public AjaxResult handleBindException(BindException e) throws Exception {checkSeataError(e);log.error(e.getMessage(), e);String message = e.getAllErrors().get(0).getDefaultMessage();return AjaxResult.error(message);}/*** 自定义验证异常*/@ExceptionHandler(MethodArgumentNotValidException.class)public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) throws Exception {checkSeataError(e);log.error(e.getMessage(), e);String message = e.getBindingResult().getFieldError().getDefaultMessage();return ResponseResult.error(message);}/*** 内部认证异常*/@ExceptionHandler(InnerAuthException.class)public AjaxResult handleInnerAuthException(InnerAuthException e) throws Exception {checkSeataError(e);return AjaxResult.error(e.getMessage());}...... } -
修改Feign熔断降级方法
由于项目对远程调用接口还做了熔断降级操作,导致调用方仍然识别不到异常,所以这里将熔断降级方法修改下,让其能正常抛异常
@Component @Slf4j public class ConstructionProviderFallback implements IConstructionProvider {@Overridepublic ResponseResult<String> testSeata(Boolean error) {if (error) {throw new RuntimeException("降级方法中---模拟被调用方异常");}return ResponseResult.success("----------------testSeata接口远程调用熔断-----------------");} } -
启动类增加AOP注解
由于全局事务注解@GlobalTransactional底层是基于AOP实现,所以需要给两个服务的启动类都加上AOP注解
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) -
调用方测试接口
/*** 测试全局事务* @return*/ @ApiOperation("测试全局事务") @GetMapping("/testSeata") @ApiImplicitParam(name = "type", value = "1:模拟调用方异常 其他:模拟被调用方异常") public ResponseResult<Boolean> testSeata(@RequestParam Integer type) {SecurityTest securityTest = new SecurityTest();securityTest.setTestColumn("测试全局事务");securityTest.setOrganizeId(1L);return ResponseResult.success(testSeataService.testSeata(type,securityTest)); }@GlobalTransactional @Override public Boolean testSeata(Integer type, SecurityTest securityTest) {log.info("seata全局事务ID: {}", RootContext.getXID());if (type!=null&&type==1) {//先远程调用construction服务保存远程服务数据constructionProvider.testSeata(false);//再保存自己服务数据securityTestService.save(securityTest);//模拟调用方异常throw new RuntimeException("模拟调用方异常");} else {//先保存自己服务数据securityTestService.save(securityTest);//再远程调用construction服务保存远程服务数据,且模拟被调用方异常constructionProvider.testSeata(true);}return true; }这里测试两种情况,调用方异常事务回滚,还有被调用方异常事务回滚
-
被调用方提供的Feign接口
@Service(value = "IConstructionProvider") @FeignClient(value = ConstructionProviderConstant.MATE_CLOUD_CONSTRUCTION, fallback = ConstructionProviderFallback.class) public interface IConstructionProvider {/*** 测试全局事务* @param error* @return*/@GetMapping(ConstructionProviderConstant.TEST_SEATA)ResponseResult<String> testSeata(@RequestParam("error") Boolean error);}这里当时遇到了一个坑 :
-
如果不写@RequestParam(“error”) ,会识别成POST请求,然后报错不支持POST请求
-
如果写了@RequestParam,但是没设置value属性,即写@RequestParam Boolean error,也会报错
参考:Feign 调用报 RequestParam.value() was empty on parameter 0-CSDN博客
实现:

正常调用:
/*** 测试Seata全局事务* @param error 是否模拟被调用方异常* @return*/ @Override @ApiOperation(value = "测试Seata全局事务", notes = "测试Seata全局事务", httpMethod = "GET") @GetMapping(ConstructionProviderConstant.TEST_SEATA) @SentinelResource(value = ConstructionProviderConstant.TEST_SEATA, fallbackClass = ConstructionProviderFallback.class, fallback = "testFeign") public ResponseResult<String> testSeata(@RequestParam(value = "error") Boolean error) {SecurityTest1 test = new SecurityTest1();test.setTestColumn("seata");test.setOrganizeId(1L);securityTestService.save(test);if (error) {throw new RuntimeException("模拟被调用方异常");}return ResponseResult.success("---------------testSeata接口正常------------------"); }熔断降级:
@Override public ResponseResult<String> testSeata(Boolean error) {if (error) {throw new RuntimeException("降级方法中---模拟被调用方异常");}return ResponseResult.success("----------------testSeata接口远程调用熔断-----------------"); } -
5.测试结果
分别测试了调用方异常、被调用方异常的情况,均能实现全局事务回滚(两边的数据库都回滚了),如下图所示


下面是seata控制台的信息(存于数据库里)

这里我测试的结果是 只有调用方和被调用方都有事务回滚 才会有信息,而且会定期清除
相关文章:
springcloud集成seata实现分布式事务
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网:Apache Seata 文章目录 一、部署1.下载2.修改配置,nacos作注册中心,db存储 二、集成到springcloud项目1.引入依赖2.修改…...
[Leetcode 61][Medium]-旋转链表
目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题链接 二、整体思路 首先发现这样的规律:当k大于等于链表中节点总数n时,会发现此时旋转后的链表和kk%n时的旋转后的链表一样。同时对于特殊情况n0和n1时,无论k的值为多少都可以…...
高效分页策略:掌握 LIMIT 语句的正确使用方法与最佳实践
本文主要介绍limit 分页的弊端及线上应该怎么用 LIMIT M,N 平时经常见到使用 <limit m,n> 合适的 order by 来实现分页查询,这样做到底性能如何呢? 先来简单分析下,然后再实际验证一下。 无索引条件下,需要做大量的文件排…...
拼图游戏02
文章目录 概要整体架构流程代码过程小结 概要 现在需要将图片添加界面中 关键点在于它如何动态地根据游戏状态更新用户界面。它使用了Swing的布局管理器来定位组件,并且通过ImageIcon和JLabel来显示图像。注意,路径字符串中的反斜杠在Java中是转义字符…...
在本地进行Django支付宝扫码支付-当面付开发
这几天涉及到一个个人项目的支付开发场景,正好完成之后,做一下开发记录,给有需要的朋友做一下参考 涉及安装Python环境请参考我专栏中的历史文章,这里不再重复说明 环境: Python3.11 使用Django框架 因本次代码为沙…...
redis-RedisTemplate.opsForGeo 的geo地理位置相关的方法演示
主要方法:add : 添加一个地理位置distance: 计算两个元素之间的距离hash: 获取元素经纬度坐标经过geohash算法生成的base32编码值position: 获取集合中任意元素的经纬度坐标,可以一次获取多个radius:查询某个坐标或某个成员&#…...
做短视频矩阵要十几人团队吗?云微客助阵,一人即可
现在市面上主流的新媒体平台都进军了短视频赛道,对于众多企业和个人来说,短视频矩阵更是成为了提升影响力和拓展业务的关键。企业或个人可以根据自身产品特点和目标用户群体,构建账号矩阵,在多平台上建立账号矩阵,还可…...
常用语音识别开源工具的对比与实践
常用语音识别开源工具的对比 一.工具概述 1. WeNet 设计目标:WeNet 的设计主要聚焦于端到端(E2E)语音识别,特别是在流式识别方面的优化。其目标是提供一个可以在实际应用中达到低延迟和高精度的系统。模型架构: Con…...
Fortify代码安全测试工具在静态应用安全测试(SAST)方面针对典型问题的改进
Fortify代码安全测试工具作为行业内资深的老牌软件安全测试工具,可以同时支持静态代码扫描和动态代码扫描,本文我们讲述的主要是在静态代码扫描领域Fortify所面临的问题,以及最新的改进。 在应用安全领域,特别是静态应用安全测试&…...
AWS 消息队列服务 SQS
AWS 消息队列服务 SQS 引言什么是 SQSSQS 访问策略 Access Policy示例:如何为 DataLake Subscription 配置 SQS 引言 应用系统需要处理海量数据,数据发送方和数据消费方是通过什么方式来无缝集成消费数据的,AWS 提供 SQS 消息队列服务来解决…...
【iOS】——响应者链和事件传递链
事件传递 事件传递流程 发生触摸事件后,系统会将该事件封装成UIEvent对象加入到一个由UIApplication管理的事件队列 UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口…...
mysql查询慢
可能是连接数,或者缓存不够,可尝试添加如下参数,重启mysql [mysqld] max_connections50000 interactive_timeout604800 lock_wait_timeout600 wait_timeout604800 net_read_timeout604800 log-error/var/lib/mysql/mysqld.log slow_qu…...
【Java-==与equals】
与equals区别: 1.是关系运算符,equals()是0bject类中定义的方法 2.基本数据类型: 使用比较值,无法使用equals() 3.引用数据类型: 使用比较内存地址; 如果没有重写equals(),仍然调用的是0bject父类的equals(()方法,则比较的是内…...
ai回答 部署前端项目时需要使用ssh吗
SSH(Secure Shell)是一种网络协议,用于在不安全的网络上安全地访问远程计算机。SSH 提供了加密的命令行登录到远程计算机的功能,以及远程命令执行和其他服务。它常用于系统管理员管理服务器、开发者进行远程开发、用户通过终端访问…...
结合ChatGPT与Discord,提高团队合作效率
本文将教你如何集成Discord Bot,助力团队在工作中实现更高效的沟通与协作。通过充分发挥ChatGPT的潜力,进一步提升工作效率和团队协作能力。无需编写任何代码即可完成本文所述的操作,进行个性化定制只需对参数进行微调即可。 方案介绍 如果在…...
VisualStudio|开发环境相关技巧及问题
哈喽,你好啊,我是雷工! 本节继续学习VisualStudio相关内容,以前学习都是以能用为主,没有系统的学习,接下来会系统的学习相关内容, 以下为学习笔记。 01 第三方dll调用 ①:如果第三…...
Redis远程字典服务器(11)—— redis客户端介绍
一,基本介绍 前面学习的主要是各种Redis的基本操作/命令,都是再Redis命令行客户端,手动执行的,但是这种方式不是我们日常开发中主要的形式更多的时候,是使用Redis的api,来实现定制化的Redis客户端程序&…...
【mysql】mysql之DDL数据定义语言
本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8…...
Word文件密码忘记,该如何才能编辑Word文件呢?
Word文件打开之后,发现编辑功能都是灰色的,无法使用,无法编辑,遇到这种情况,是因为Word文件设置了限制编辑导致的。一般情况下,我们只需要输入Word密码,将限制编辑取消就可以正常编辑文件了&…...
解锁移动办公新境界,七款顶尖移动终端管控软件分享!助您轻松掌控每一台移动设备,企业必备!
移动办公,它不仅打破了时间和空间的限制,提高了工作效率,还为员工创造了更加灵活的工作环境。然而,随着移动设备的普及,如何有效管理和控制这些终端,确保信息安全、提升工作效率呢? 今天&#…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
