Seata1.5.2解决分布式事务问题

2023-09-16 13:20:05

分布式事务–Seata

​ 前面了解到一些分布式事务的解决方案,业内也涌现出不少解决分布式事务的优秀框架,如Atomikos、Seata等,本章来了解使用下Seata。

​ Seata的前身是Fescar,而后改名Seata,简单可扩展的自治分布式事务框架。Seata为用户提供了AT、TCC、SAGA和XA事务模式(默认使用AT),致力打造的一站式分布式解决方案。

​ Seata是在传统的2PC方案上进行演进,它把一个分布式事务拆分为若干个分支事务的全局事务,全局事务协调管理若干个分支事务,使其达到一致,实现整个事务那么全部成功,要么全部失败,并且在项目中整合Seata几乎没有侵入性。

基本概念

Seata有几个基本的概念:

  • Transaction ID XID:全局唯一事务ID
  • TC(Transaction Coordinator):事务协调者,维护全局事务运行,驱动全局事务的提交和回滚
  • TM(Transaction Manager):事务管理器,定义全局事务边界,负责开启全局事务,发起全局事务的提交或回滚决议
  • RM(Resource Manager):资源管理器,管理分支事务,与TC(事务协调者)通信,决定对分支事务提交或回滚

Seata的下载和安装

Seata像Nacos一样,也有自己的服务端,需要下载服务端程序,地址是:https://github.com/seata/seata/releases

那么如何选择版本呢,还是要按照SpringCloudAlibaba的组件版本对应关系来使用.

在这里插入图片描述

笔者使用的alibaba是2021.0.4.0,seata就使用1.5.2版本即可,下载速度会挺慢,国外网址可以翻墙或者用迅雷等软件下载都可。

conf目录中,有一个application.example.yml文件,内容是关于seata的注册和配置的示例,可以修改下且改名为application.yml。

  • 可以看到配置文件中实现Seata的注册和配置方式有几种:File、Nacos、Eureka、Redis、Consul等等。我们使用Nacos即可。application.yml配置如下:
server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata

# 新增加的console控制台,
# 可通过访问http://localhost:7091进行登录,账号如下seata/seata
console:
  user:
    username: seata
    password: seata

seata:
  config:
    # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ded91f4b-04df-4c19-8006-755505a27c5e
      group: SEATA_GROUP
      username: nacos
      password: nacos
      # data-id: seataServer.properties
  registry:
    # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: ded91f4b-04df-4c19-8006-755505a27c5e
      cluster: default
      username: nacos
      password: nacos
  # seata的安全配置
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
  • 到数据库中新增需要的数据表【Seata 1.5.2版本mysql脚本】导入压缩包目录seata/script/db/mysql.sql

在这里插入图片描述

数据表创建完毕

  • 启动nacos,创建一个dev的命名空间方便测试

在这里插入图片描述

  • 修改压缩包目录seata/script/config-center/config.txt文件中几处内容:
# 存储模式
store.mode=db
 
store.db.datasource=druid
store.db.dbType=mysql
# 需要根据mysql的版本调整driverClassName
# mysql8及以上版本对应的driver:com.mysql.cj.jdbc.Driver
# mysql8以下版本的driver:com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.jdbc.Driver
# 注意根据生产实际情况调整参数host和port
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
# 数据库用户名密码
store.db.user=root
store.db.password=123456
# 微服务里配置与这里一致
service.vgroupMapping.dev_tx_group=default

TIPS📢:
配置事务分组service.vgroupMapping.dev_tx_group=default
dev_tx_group:需要与客户端保持一致 ,可以自定义
default:需要跟客户端和application.yml中的cluster保持一致
default 必须要等于 registry.conf cluster = “default”

  • 官方推荐通过压缩包目录seatascript/config-center/nacos/nacos-config.sh将修改后的config.txt发布到nacos上
# 运行指令 ,通过 Git Bash Here
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t ded91f4b-04df-4c19-8006-755505a27c5e

# 具体说明参见:http://seata.io/zh-cn/docs/user/configurations.html
# -h: nacos host,默认localhost
# -p: nacos端口,默认8848
# -g: nacos分组,默认'SEATA_GROUP'.
# -t: 租户信息Tenant information,对应nacos namespace ID,默认''
# -u: nacos用户名,默认''
# -w: nacos用户密码,默认''

可以看到,配置自动导入到了nacos中

在这里插入图片描述

到seata的bin目录下执行seata-server.sh或bat即可执行服务端。

客户端

那么还是按照上篇文章中的订单服务配送服务来实现。

  • 项目中导入依赖

父工程

<properties>
    <java.version>8</java.version>
    <boot.version>2.6.11</boot.version>
    <cloud.version>2021.0.4</cloud.version>
    <cloud.alibaba.version>2021.0.4.0</cloud.alibaba.version>
</properties>
<dependencyManagement>
    <dependencies>
        <!--springboot依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--cloud的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--cloud.alibaba依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${cloud.alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 子工程(订单服务和配送服务都要)
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--seata starter -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
</dependency>
  • 订单数据库配送数据库增加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;

注意📢:每个业务数据库都要有UNDO_LOG

  • 订单服务和配送服务的yml文件主要引入的配置
    • seata.enabled:是否开启自动装配seata
    • seata.application-id:应用id
    • seata.tx-service-group:事务分组
    • seata.enable-auto-datasouce-proxy:数据源自动代理
spring:
  cloud:
    nacos:
      discovery:
        group: SEATA_GROUP
        server-addr: http://localhost:8848
        # 必须填命名空间的ID
        namespace: ded91f4b-04df-4c19-8006-755505a27c5e

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///order?useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456

distribution:
  name: distribution


# Seata 配置
seata:
  application-id: order-server  # 自定义
  # 是否启用数据源bean的自动代理
  enable-auto-data-source-proxy: false
  tx-service-group: dev_tx_group  # 必须和服务器配置一样
  registry:
    type: nacos
    nacos:
      # Nacos 服务地址
      server-addr: http://localhost:8848
      group: SEATA_GROUP
      namespace: ded91f4b-04df-4c19-8006-755505a27c5e
      application: seata-server # 必须和服务器配置一样
      username: nacos
      password: nacos
      cluster: default
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      group: SEATA_GROUP
      namespace: ded91f4b-04df-4c19-8006-755505a27c5e
  service:
    vgroup-mapping:
      tx-service-group: dev_tx_group # 必须和服务器配置一样
    disable-global-transaction: false
  client:
    rm:
      # 是否上报成功状态
      report-success-enable: true
      # 重试次数
      report-retry-count: 5

feign:
  client:
    config:
      default:
        connectTimeout: 2000 # 建立连接超时时间
        readTimeout: 2000   # 读取资源超时时间
  • 配置代理数据源
@Primary
@Bean
public DataSource dataSource() {
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    druidDataSource.setUrl("jdbc:mysql:///distribution?useUnicode=true&characterEncoding=utf-8");
    druidDataSource.setUsername("root");
    druidDataSource.setPassword("123456");
    DataSourceProxy dsp = new DataSourceProxy(druidDataSource);
    return dsp;
}
  • 在事务开始的地方使用注解@GlobalTransactional
@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    JdbcTemplate jdbcTemplate;  // 操作数据库
    @Resource
    DistributionService distributionService;    // 远程调用

    // 模拟菜品数据
    private final Map<Integer, String> shopMap = new HashMap<Integer, String>(){{
       put(1,"菜品1");
       put(2,"菜品2");
       put(3,"菜品3");
    }};

    @Override
    @Transactional  // 加上事务
    @GlobalTransactional // seata全局事务
    public Integer createOrder(Integer id) {
        if (shopMap.containsKey(id)) {
            String orderId = UUID.randomUUID().toString().replace("-","");
            // 增加订单
            int update = jdbcTemplate.update("insert into t_order(order_id, shop_id) values(?,?)",
                    new Object[]{orderId, id});
            // 调用配送服务
            Integer result = distributionService.distribution(orderId);
            if (result <= 0) {
                throw new RuntimeException("分配配送员失败");
            }
            return update;
        }
        return null;
    }
}

踩坑

  • 前面在seata的yml中可以看到使用的是druid的连接池,mybatisPlus默认集成了druid和hikari两种连接池,而mybatis不是,因此需要在使用mybatis的项目中,另外集成druid,在application.yml中声明datasource.type为"com.alibaba.druid.pool.DruidDataSource"

  • seata服务端的配置尽量和客户端做到一致,如driver_class_name:“com.mysql.cj.jdbc.Driver”

测试

curl localhost:9002/createOrder?id=1
{"timestamp":"2023-09-16T04:59:01.614+00:00","status":500,"error":"Internal Server Error","path":"/createOrder"}

可以看到返回报错,同样去看下两个服务的日志情况:订单服务调用配送服务

在这里插入图片描述

可看到,订单服务在调用配送服务5s后,直接超时并且开始回滚数据。

配送服务日志:

在这里插入图片描述

由于本案例是一个超时情况,所以说这里报错是正常的,因为是配送服务还没执行完毕,订单服务就已经去回滚数据了,配送服务执行完毕后收到回滚的信号,也去进行回滚,发现这条xid的数据已经被订单服务回滚过了,报错没找到此XID的数据。

更多推荐

在 Swift 中使用 async let 并发运行后台任务

文章目录前言长期运行的任务阻塞了UI使用async/await在后台执行任务在后台执行多个任务使用"asynclet"下载多个文件结论前言Async/await语法是在Swift5.5引入的,在WWDC2021中的Meetasync/awaitinSwift对齐进行了介绍。它是编写异步代码的一种更可读的方式,比调度队列

基于yolov5的交通标志牌的目标检测研究设计——思路及概念

有需要项目的可以私信博主!!!!!一、选题的目的、意义及研究现状(1)选题的目的和意义随着人们对道路安全性的重视和城市交通量的不断增加,交通标志牌作为道路交通安全的重要组成部分之一,扮演着十分重要的角色。然而,交通标志牌的数量庞大、种类繁多,人工巡视的方式存在效率低、漏检率高等问题,对道路交通的安全性带来了一定的隐患。

Vue模板语法(下)

一.事件处理器什么是事件处理器建立一个HTML编写事件处理器测试结果二.表单的综合案例什么是表单综合案例建立一个HTML来编写表单案例测试结果三.局部组件什么是组件通信自定义组件测试结果组件通信-父传子测试结果组件通信-子传父测试结果一.事件处理器什么是事件处理器事件处理器是一种用于响应和处理用户交互事件的机制。在We

zookeeper最基础教程

文章目录一、简介1、工作机制2、特点3、数据结构4、应用场景5、选举机制二、软件安装1、单机版安装2、集群安装3、配置参数解读(zoo.cfg)4、ZK集群启动脚本三、命令行操作1、语法2、使用3、节点相关4、监听器原理5、节点删除与查看三、写数据流程一、简介1、工作机制官方地址:https://zookeeper.a

SpringMVC之JSON数据返回与异常处理机制---全方面讲解

一,JSON数据返回的理解在SpringMVC中,当需要将数据以JSON格式返回给客户端时,可以使用@ResponseBody注解或@RestController注解将Controller方法的返回值直接转化为JSON格式并返回。这使得开发者可以方便地将Java对象转换为JSON,并通过HTTP响应返回给客户端。Spr

TCP协议

文章目录TCP协议1.TCP协议段格式(1)2个核心问题(解包与分用)(2)TCP的可靠性2.确认应答(ACK)机制3.16位窗口大小4.6位标志位(1)ACK(2)SYN(3)FIN(4)PSH(5)RST(6)URG5.超时重传机制6.连接管理机制6.0TCP协议通讯流程6.1三次握手(1)前置问题(2)细节问题(

面试题三:请你谈一谈Vue中的filter功能的实现

Vue中过滤器(filter)的使用我们想一下有methods为什么要有filter的存在呢,因为filter的实现效率比methods要高的多。看一下官方定义:Vue.js允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和v-bind表达式(后者从2.1.0+开始支持)。过滤器应

Vue3 学习-组件通讯(二)

文章目录前言一、props通信二、自定义事件(emit)三、全局事件总线(EventBus)四、v-model五、userAttrs六、ref和$parent七、Provide与Inject八、pinia九、slot插槽总结前言本文主要记录Vue3的九种组件通信方式一、props通信子组件需要用defineProps方

springboot日志配置(logback+slf4j配置)

1.为什么要配置日志故障排查和问题分析:日志记录允许开发人员和运维人员在系统发生问题或故障时追踪问题的根本原因。通过查看日志文件,他们可以了解系统在特定时间点发生了什么事情,从而更容易定位和解决问题。性能监控和优化:日志记录可以帮助监控应用程序和系统的性能。通过分析日志数据,你可以识别性能瓶颈和瓶颈的位置,从而采取相应

RPA和传统自动化的区别?

随着科技的不断发展,企业对于自动化工具的需求也在逐渐增加。在自动化领域中,传统的自动化技术和现代的RPA(RoboticProcessAutomation)都是重要的自动化工具,但它们之间存在一些明显的区别。本文将从技术、应用和未来趋势三个方面来探讨RPA和传统自动化的区别。一、技术上的区别自动化方式传统的自动化技术通

Java安全入门笔记(持续更新)

之前陆陆续续学过一点Java安全,笔记一直都没没有系统的写过,现在重新深入学一下之前的知识,会把笔记持续更新过来Java反射反射是java得一个重要特性,它可以获取一个类的所有信息,还可以执行类中的方法反射赋予Java动态特性我个人感觉静态语言的安全性是比较高的,因为一个供给使用的静态语言的程序的结构时固定的,能给攻击

热文推荐