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密码,将限制编辑取消就可以正常编辑文件了&…...
解锁移动办公新境界,七款顶尖移动终端管控软件分享!助您轻松掌控每一台移动设备,企业必备!
移动办公,它不仅打破了时间和空间的限制,提高了工作效率,还为员工创造了更加灵活的工作环境。然而,随着移动设备的普及,如何有效管理和控制这些终端,确保信息安全、提升工作效率呢? 今天&#…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
