Spring cloud Alibaba Seata solves distributed transactions

Spring cloud Alibaba Seata solves distributed transactions

Seata is an open source distributed transaction solution of Alibaba. It is committed to providing high-performance and easy-to-use distributed transaction services. This paper will introduce its usage in detail through a simple single business scenario.

What is a distributed transaction problem?

Monomer application

In a single application, a business operation needs to call three modules to complete. At this time, the consistency of data is guaranteed by local transactions.

Microservice application

With the change of business requirements, individual applications are split into micro service applications. The original three modules are split into three independent applications, using independent data sources respectively. Business operations need to call three services to complete. At this time, the data consistency within each service is guaranteed by local transactions, but the global data consistency cannot be guaranteed.

Summary

In the microservice architecture, the problem that global data consistency cannot be guaranteed is the distributed transaction problem. In short, a business operation needs to operate multiple data sources or make remote calls, which will lead to the problem of distributed transactions.

Introduction to Seata

Seata is an open source distributed transaction solution, which is committed to providing high-performance and easy-to-use distributed transaction services. Seata will provide users with AT, TCC, SAGA and XA transaction modes to create a one-stop distributed solution for users.

At present, RPC frameworks such as Dubbo, Spring Cloud, sofa RPC, Motan and grpc are supported, and other frameworks continue to be integrated

Seata principle and design

Define a distributed transaction

We can understand a distributed transaction as a global transaction containing several branch transactions. The responsibility of a global transaction is to coordinate the branch transactions under its jurisdiction to reach an agreement, either successfully commit together or fail to roll back together. In addition, the branch transaction itself is usually a local transaction that satisfies ACID. This is our basic understanding of distributed transaction structure, which is consistent with XA.

Protocol three components of distributed transaction processing

  • Transaction Coordinator (TC): Transaction Coordinator, which maintains the running state of global transactions, coordinates and drives the submission or rollback of global transactions;
  • Transaction Manager ™: Control the boundary of global transaction, start a global transaction, and finally initiate the resolution of global commit or global rollback;
  • Resource Manager (RM): controls branch transactions, is responsible for branch registration and status reporting, receives instructions from the transaction coordinator, and drives the submission and rollback of branch (local) transactions.

A typical distributed transaction process

  • TM applies to TC to start a global transaction. The global transaction is successfully created and a globally unique XID is generated;
  • XID propagates in the context of the microservice invocation link;
  • RM registers branch transactions with TC and brings them under the jurisdiction of global transactions corresponding to XID;
  • TM initiates a global commit or rollback resolution for XID to TC;
  • TC schedules all branch transactions under XID to complete the commit or rollback request.

Installation and configuration of Seata server

Using environment CentOS 7 + java 8

Seata 1.4.0 (key configurations and missing files have been completed)

  • Link: https://pan.baidu.com/s/1JHO1xWlk2ZO1gf88rUgYdA
    Extraction code: 1234

Then unzip the file nuzip -o seata1.4.0.zip in CentOS 7 /usr/src /

Then other specific steps are as follows:

  • Initialize seata database
  • Here, we use Nacos as the registration center and configuration center. For the installation and use of Nacos, please refer to the centos7 Nacos service registration center
  • Push the configuration in the config.txt file to the nacos configuration center
  • Modify the registry.conf configuration file in the conf directory
  • Start seata and register the service with Nacos

Initialize database

Create the database seata and import the following three tables

-- 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`)
);

The following table needs to be added to each business database that requires distributed transactions:

-- This script must be initialized in your current business database for AT pattern XID record. And server End independent (Note: business database)
-- Note here 0.3.0+ Add unique index 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;

Push the configuration in the config.txt file to the nacos configuration center

cd /usr/src/seata

Modify config.txt content

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

Be sure to change the ip of the database to the real ip instead of 127.0.0.1

The rest are on the line by default

Then go to cd /usr/src/seata/conf for execution

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

It should print after execution

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

Parameter interpretation

-h nacos registry address

-p nacos registry port

-G groups registered in Nacos (default SEATA_GROUP)

-u nacos registry account number

-w nacos registry password

Modify the registry.conf file

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"
  }  

Then enter

cd /usr/src/seata/bin

seata-server.sh the default JVM configuration is

-server -Xmx2048m -Xms2048m -Xmn1024m -Xss512k

Generally, the test machine does not have such a large content, so we need to reduce it to find the position of about 120 lines, and change it to

-server -Xmx512m -Xms512m -Xmn256m -Xss256k

Then execute:

chmod +x ./seata-server.sh

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

  • h: Specify the host IP of the Seata server
  • p: Specify the port number of the Seata app

If you look at the log and look at the last line printed, the result is similar to the following

xxxx... Server started, listen port: 8092

It means that the seata configuration is started successfully, and the rest is about how to use the project

If necessary, view and close the project

Using distributed transactions in projects

Project structure

Only the key code is written below. In fact, other files have been learned in previous tutorials. It doesn't matter whether they will or not

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 Version should be higher than spring-cloud-alibaba Otherwise, an error is reported-->
    <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 service-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.1</version>
        </dependency>
        <!--        springboot test Start component-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.1.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!--        Register service to Nacos in-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>


        <!--Mybati and Spring boot Automatically integrate dependencies -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--Database driver tell Springboot We use mysql database-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

                 <!-- Distributed transaction -->       
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!-- Exclude dependencies. The specified version is consistent with the server side -->
                <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>

Main project: 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  #I used nginx as the cluster agent for the registry address


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





seata:
  enabled: true # Just default
  tx-service-group: my_test_tx_group   #The default group is my_test_tx_group, which can be left untouched
  enable-auto-data-source-proxy: true  # Just default
  registry:
    type: nacos  # Just default
    nacos:
      server-addr: 192.168.81.129:8848     # Change to your own Nacos
      #      namespace: seata_namespace_id
      group: SEATA_GROUP    # Just default
  config:
    type: nacos # Just default
    nacos:
      server-addr: 192.168.81.129:8848    # Change to your own 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   //Must be added, otherwise the seata distributed transaction is invalid
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  correct
    // http://localhost:10601/seata1/save?id=0   Trigger transaction
    @Autowired
    private UserMapper userMapper;
    @GetMapping("/save")
    @GlobalTransactional(rollbackFor = Exception.class)  //Open distributed transaction
    public  String save(@RequestParam("id") Integer id){

        User build = User.builder().name("Your mother 11111").age(22).sex("female").build();
        userMapper.save(build);  //Add data

        restTemplateUtils.delete("http://nacos-seata2/seata2/delete?id="+id,String.class); / / call the remote service to delete data

        User build1 = User.builder().id(4).name("xx").age(21).sex("male").build();
        userMapper.update(build1);  //Modify data
        return "success";
    }

}

Sub project: Nacos seata2

application.yml

The basic configuration is the same as the main project. Just replace the port

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 #Address of Registration Center

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();
    }

}

test

http://localhost:10601/seata1/save?id=3 correct

http://localhost:10601/seata1/save?id=0 The transaction is triggered, and all service interfaces roll back the operation

Like - collect - pay attention Easy to review and receive the latest content in the future If you have other questions to discuss in the comment area - or send me a private letter - you will reply as soon as you receive them Thank you for your cooperation. I hope my efforts will be helpful to you^_^

Tags: Java Spring Cloud Microservices seata

Posted on Mon, 20 Sep 2021 02:31:14 -0400 by dotBz