天天看点

SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

SpringCloud-Alibaba-Seata解决分布式事务

Seata是Alibaba开源的一款分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,本文将通过一个简单的下单业务场景来对其用法进行详细介绍。

什么是分布式事务问题?

单体应用

单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。

SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

微服务应用

随着业务需求的变化,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

小结

在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。

Seata简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其他框架持续集成中

Seata原理和设计

定义一个分布式事务

我们可以把一个分布式事务理解成一个包含了若干分支事务的全局事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足ACID的本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。

SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

协议分布式事务处理过程的三个组件

  • Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
  • Transaction Manager ™:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  • Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

一个典型的分布式事务过程

  • TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
  • XID 在微服务调用链路的上下文中传播;
  • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  • TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

seata-server的安装与配置

使用环境CentOS 7 + java 8

seata1.4.0 (关键配置和缺失的文件都已经补全)

  • 链接:https://pan.baidu.com/s/1JHO1xWlk2ZO1gf88rUgYdA

    提取码:1234

然后在CentOS 7 /usr/src/ 目录下解压文件

nuzip -o seata1.4.0.zip

然后其他具体步骤如下:

  • 初始化seata数据库
  • 这里我们使用Nacos作为注册中心和配置中心,Nacos的安装及使用可以参考:

    Centos7-Nacos-服务注册中心搭建

  • 将config.txt文件内的配置推送到nacos配置中心中
  • 修改

    conf

    目录下的

    registry.conf

    配置文件
  • 启动seata将服务注册到Nacos中

初始化数据库

创建数据库seata然后导入下面这3张表

-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `global_table` (
  `xid` varchar(128)  not null,
  `transaction_id` bigint,
  `status` tinyint not null,
  `application_id` varchar(32),
  `transaction_service_group` varchar(32),
  `transaction_name` varchar(128),
  `timeout` int,
  `begin_time` bigint,
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`xid`),
  key `idx_gmt_modified_status` (`gmt_modified`, `status`),
  key `idx_transaction_id` (`transaction_id`)
);

-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
  `branch_id` bigint not null,
  `xid` varchar(128) not null,
  `transaction_id` bigint ,
  `resource_group_id` varchar(32),
  `resource_id` varchar(256) ,
  `lock_key` varchar(128) ,
  `branch_type` varchar(8) ,
  `status` tinyint,
  `client_id` varchar(64),
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`branch_id`),
  key `idx_xid` (`xid`)
);

-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
  `row_key` varchar(128) not null,
  `xid` varchar(96),
  `transaction_id` long ,
  `branch_id` long,
  `resource_id` varchar(256) ,
  `table_name` varchar(32) ,
  `pk` varchar(36) ,
  `gmt_create` datetime ,
  `gmt_modified` datetime,
  primary key(`row_key`)
);

           

在每一个需要分布式事务的业务数据库中都需要添加下面这表:

-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `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;
           

将config.txt文件内的配置推送到nacos配置中心中

cd /usr/src/seata

修改config.txt内容

store.mode=db

...........................................

store.db.url=jdbc:mysql://192.168.0.102:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
           

一定要把数据库的ip改为真实ip 而不是127.0.0.1

其余的默认就行

然后进入到 cd /usr/src/seata/conf 执行

sh nacos-config.sh -h 192.168.81.129 -p 8848 -g SEATA_GROUP -u nacos -w nacos

执行完后应该会打印

=========================================================================
 Complete initialization parameters,  total-count:94 ,  failure-count:4 
=========================================================================
           

参数解释

-h nacos注册中心地址

-p nacos注册中心端口

-g nacos中注册的分组(默认 SEATA_GROUP)

-u nacos注册中心账号

-w nacos注册中心密码

修改registry.conf文件

cd /usr/src/seata/conf
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "192.168.81.129:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
  
  ..............................................
  
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "192.168.81.129:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }  
           

然后进入

cd /usr/src/seata/bin

seata-server.sh默认JVM配置是

-server -Xmx2048m -Xms2048m -Xmn1024m -Xss512k

一般测试机器没有这么大的内容所以我们需要改小点 找到大约120行左右位置,改为

-server -Xmx512m -Xms512m -Xmn256m -Xss256k

然后执行:

chmod +x ./seata-server.sh

nohup ./seata-server.sh -h 192.168.81.129 -p 8092 &

  • h : 指定Seata服务器的主机IP
  • p : 指定Seata应用的端口号

如果查看日志看打印的最后一行结果类似如下

xxxx… Server started, listen port: 8092

那么就代表seata配置启动成功,剩下的就该项目如何进行使用了

如果需要查看和关闭项目

项目中使用分布式事务

项目结构

SpringCloud-Alibaba-Seata解决分布式事务SpringCloud-Alibaba-Seata解决分布式事务

下面只写关键代码写,其他文件其实在以前的教程中都学过了,会不会都无关紧要

Maven

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring.cloud.alibaba.version>2.2.3.RELEASE</spring.cloud.alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!--     Springboot 版本要高于 spring-cloud-alibaba 否则报错-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
    </parent>

    <dependencies>

        <!-- spring-cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR8</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--    web服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.1</version>
        </dependency>
        <!--        springboot test启动组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.1.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!--        将服务注册到Nacos里-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>


        <!--Mybati 和Spring boot 自动整合依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--数据库驱动 告诉Springboot 我们使用mysql数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

                 <!-- 分布式事务 -->       
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!-- 排除依赖 指定版本和服务器端一致 -->
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.0</version>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
           

主项目: nacos-seata1

application.yml

server:
  port: 10601

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/voidme?characterEncoding=UTF-8
  application:
    name: nacos-seata1
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.81.129:8848  #注册中心地址 我使用nginx做了集群代理了


mybatis:
  type-aliases-package: com.springbootalibaba.nacos.pojo
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true





seata:
  enabled: true # 默认就行
  tx-service-group: my_test_tx_group   #默认分组就是 my_test_tx_group这个可以不要动
  enable-auto-data-source-proxy: true  # 默认就行
  registry:
    type: nacos  # 默认就行
    nacos:
      server-addr: 192.168.81.129:8848     # 改为自己的Nacos
      #      namespace: seata_namespace_id
      group: SEATA_GROUP    # 默认就行
  config:
    type: nacos # 默认就行
    nacos:
      server-addr: 192.168.81.129:8848    # 改为自己的Nacos
      #      namespace: seata_namespace_id
      group: SEATA_GROUP

           

SeataApplication

package com.seata1;

import io.seata.spring.annotation.datasource.EnableAutoDataSourceProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoDataSourceProxy   //必须添加否则seata分布式事务无效
public class SeataApplication {

    public static void main(String[] args) {
        SpringApplication.run(SeataApplication.class,args);
    }


}

           

UserController

package com.seata1.controller;

import com.seata1.dao.UserMapper;
import com.seata1.pojo.User;
import com.seata1.utils.RestTemplateUtils;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("seata1")
public class UserController {


    @Autowired
    private RestTemplateUtils restTemplateUtils;
    //http://localhost:10601/seata1/save?id=3 正确
    // http://localhost:10601/seata1/save?id=0  触发事务
    @Autowired
    private UserMapper userMapper;
    @GetMapping("/save")
    @GlobalTransactional(rollbackFor = Exception.class)  //开启分布式事务
    public  String save(@RequestParam("id") Integer id){

        User build = User.builder().name("你媽11111").age(22).sex("女").build();
        userMapper.save(build);  //添加数据

        restTemplateUtils.delete("http://nacos-seata2/seata2/delete?id="+id,String.class); //调用远程服务的删除数据

        User build1 = User.builder().id(4).name("xx").age(21).sex("男").build();
        userMapper.update(build1);  //修改数据
        return "success";
    }

}

           

副项目: nacos-seata2

application.yml

基本配置和主项目一样 把端口换下就行了

server:
  port: 10602

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/voidme?characterEncoding=UTF-8
  application:
    name: nacos-seata2
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.81.100:80 #注册中心地址

mybatis:
  type-aliases-package: com.springbootalibaba.nacos.pojo
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true


seata:
  enabled: true
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  registry:
    type: nacos
    nacos:
      server-addr: 192.168.81.100:80
#      namespace: seata_namespace_id
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 192.168.81.100:80
#      namespace: seata_namespace_id
      group: SEATA_GROUP

           

SeataApplication

package com.seata1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableDiscoveryClient
public class SeataApplication {

    public static void main(String[] args) {
        SpringApplication.run(SeataApplication.class,args);
    }
}
           

UserController

package com.seata1.controller;

import com.seata1.dao.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("seata2")
public class UserController {

    //http://localhost:10602/seata2/delete?id=1
    @Autowired
    private UserMapper userMapper;
    @DeleteMapping("/delete")
    public  String delete(@RequestParam("id") Integer id)
    {
        if (id==0) {
            int i=1/0;
        }

        return  userMapper.delete(id).toString();
    }

}

           

测试

http://localhost:10601/seata1/save?id=3 正确

http://localhost:10601/seata1/save?id=0 触发事务,所有服务接口都回滚操作

点赞 -收藏加 -关注 便于以后复习和收到最新内容 有其他问题在评论区讨论-或者私信我-收到会在第一时间回复 感谢,配合,希望我的努力对你有帮助^_^