文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Seata使用教程

2023-08-16 16:11

关注

一、Seata简介

1.Seata 概念介绍

Seata 是一款阿里巴巴开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

官方文档地址 http://seata.io/zh-cn/docs/overview/what-is-seata.html

2.分布式事务

我们可以将分布式事务理解成一个包含了若干个分支事务的全局事务。全局事务的职责是协调其管辖的各个分支事务达成一致,要么一起成功提交,要么一起失败回滚。通常分支事务本身就是一个满足 ACID 特性的本地事务。
分布式事务主要涉及以下概念:
事务:由一组操作构成的可靠、独立的工作单元,事务具备 ACID 的特性,即原子性、一致性、隔离性和持久性。
本地事务:本地事务由本地资源管理器(例如 MySQL、Oracle 等)管理,严格地支持 ACID 特性,高效可靠。本地事务不具备分布式事务的处理能力,只能对自己数据库的操作进行控制,对于其他数据库的操作则无能为力。
全局事务:全局事务指的是一次性操作多个资源管理器完成的事务,由一组分支事务组成。
分支事务:在分布式事务中,就是一个个受全局事务管辖和协调的本地事务。

3.Seata核心组件

(1)TC(Transaction Coordinator):事务协调器,它是事务的协调者(这里指的是 Seata 服务器),主要负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
(2)TM(Transaction Manager):事务管理器,它是事务的发起者,负责定义全局事务的范围,并根据 TC 维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议。
(3)RM(Resource Manager):资源管理器,它是资源的管理者(这里可以将其理解为各服务使用的数据库)。它负责管理分支事务上的资源,向 TC 注册分支事务,汇报分支事务状态,驱动分支事务的提交或回滚。

4.Seata 工作流程

Seata 对分布式事务的协调和控制,主要是通过 XID 和 3 个核心组件实现的。
XID:是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中
工作流程:
(1)TM 向 TC 申请开启一个全局事务,全局事务创建成功后,TC 会针对这个全局事务生成一个全局唯一的 XID;
(2)XID 通过服务的调用链传递到其他服务;
(3)RM 向 TC 注册一个分支事务,并将其纳入 XID 对应全局事务的管辖;
(4)TM 根据 TC 收集的各个分支事务的执行结果,向 TC 发起全局事务提交或回滚决议;
(5)TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作。

概况一下就是每个分支事务都会有一个XID,全局事务会通过TC调度将所有XID相同的分支事务提交或者回滚。

5.Seata四大模式

四大模式分别是AT、TCC、SAGA 和 XA

1 AT模式(最常用、无业务入侵)
(1)使用前提:
①必须使用支持本地 ACID 事务特性的关系型数据库,例如 MySQL、Oracle 等;
②应用程序必须是使用 JDBC 对数据库进行访问的 JAVA 应用。
③创建一个 UNDO_LOG(回滚日志)表。(不同数据库建表语句不同,这里以mysql为例)
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,
PRIMARY KEY (id),
UNIQUE KEY ux_undo_log (xid,branch_id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

在后面提到的回滚日志中可以知道各个字段的作用

(2)AT模式的实现流程:
①获取 SQL 的基本信息,生成回滚日志,插入到 UNDO_LOG 表中,示例回滚日志如下。

{"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"2.0.1.47:8091:5791972543984050625","branchId":5791972543984050628,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"INSERT","tableName":"t00_user","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","tableName":"t00_user","rows":["java.util.ArrayList",[]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"t00_user","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList"[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"user_id","keyType":"PRIMARY_KEY","type":12,"value":"338"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"user_name","keyType":"NULL","type":12,"value":"灏忕帇"}]]}]]}}]]}

②注册所有分支事务,生成行锁
③提交或回滚
提交:若所有分支事务都执行成功,TM 向 TC 发起全局事务的提交,并批量删除各个 RM 保存的 UNDO_LOG 记录和行锁,否则全局事务回滚;
回滚:
a.通过 XID 和分支事务 ID(Branch ID) 查找所有的 UNDO_LOG 记录;
b.数据校验:将 UNDO_LOG 中的后镜像数据(afterImage)与当前数据进行比较,如果有不同,则说明数据被当前全局事务之外的动作所修改,需要人工对这些数据进行处理;
c.生成回滚语句:根据 UNDO_LOG 中的前镜像(beforeImage)和业务 SQL 的相关信息生成回滚语句;
d.还原数据:执行回滚语句,并将前镜像数据、后镜像数据以及行锁删除;
e.提交事务:提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

二、Seata实战教程

1.下载资源

下载seata-server-XXX.zip和Srouce code
https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip
https://github.com/seata/seata/archive/refs/tags/v1.4.2.zip

2.配置Seata-Server

(1)将seata-1.4.2\script\config-center\config.txt 复制到 seata-server-1.4.2\ 目录下
替换下面几行:
service.vgroupMapping.tx_service_default_group=default
store.mode=db
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root

transport.type=TCPtransport.server=NIOtransport.heartbeat=truetransport.enableClientBatchSendRequest=falsetransport.threadFactory.bossThreadPrefix=NettyBosstransport.threadFactory.workerThreadPrefix=NettyServerNIOWorkertransport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandlertransport.threadFactory.shareBossWorker=falsetransport.threadFactory.clientSelectorThreadPrefix=NettyClientSelectortransport.threadFactory.clientSelectorThreadSize=1transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThreadtransport.threadFactory.bossThreadSize=1transport.threadFactory.workerThreadSize=defaulttransport.shutdown.wait=3service.vgroupMapping.tx_service_default_group=defaultservice.default.grouplist=127.0.0.1:8091service.enableDegrade=falseservice.disableGlobalTransaction=falseclient.rm.asyncCommitBufferLimit=10000client.rm.lock.retryInterval=10client.rm.lock.retryTimes=30client.rm.lock.retryPolicyBranchRollbackOnConflict=trueclient.rm.reportRetryCount=5client.rm.tableMetaCheckEnable=falseclient.rm.tableMetaCheckerInterval=60000client.rm.sqlParserType=druidclient.rm.reportSuccessEnable=falseclient.rm.sagaBranchRegisterEnable=falseclient.tm.commitRetryCount=5client.tm.rollbackRetryCount=5client.tm.defaultGlobalTransactionTimeout=60000client.tm.degradeCheck=falseclient.tm.degradeCheckAllowTimes=10client.tm.degradeCheckPeriod=2000store.mode=dbstore.publicKey=store.file.dir=file_store/datastore.file.maxBranchSessionSize=16384store.file.maxGlobalSessionSize=512store.file.fileWriteBufferCacheSize=16384store.file.flushDiskMode=asyncstore.file.sessionReloadReadSize=100store.db.datasource=druidstore.db.dbType=mysqlstore.db.driverClassName=com.mysql.cj.jdbc.Driverstore.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=truestore.db.user=rootstore.db.password=rootstore.db.minConn=5store.db.maxConn=30store.db.globalTable=global_tablestore.db.branchTable=branch_tablestore.db.queryLimit=100store.db.lockTable=lock_tablestore.db.maxWait=5000store.redis.mode=singlestore.redis.single.host=127.0.0.1store.redis.single.port=6379store.redis.sentinel.masterName=store.redis.sentinel.sentinelHosts=store.redis.maxConn=10store.redis.minConn=1store.redis.maxTotal=100store.redis.database=0store.redis.password=store.redis.queryLimit=100server.recovery.committingRetryPeriod=1000server.recovery.asynCommittingRetryPeriod=1000server.recovery.rollbackingRetryPeriod=1000server.recovery.timeoutRetryPeriod=1000server.maxCommitRetryTimeout=-1server.maxRollbackRetryTimeout=-1server.rollbackRetryTimeoutUnlockEnable=falseclient.undo.dataValidation=trueclient.undo.logSerialization=jacksonclient.undo.onlyCareUpdateColumns=trueserver.undo.logSaveDays=7server.undo.logDeletePeriod=86400000client.undo.logTable=undo_logclient.undo.compress.enable=trueclient.undo.compress.type=zipclient.undo.compress.threshold=64klog.exceptionRate=100transport.serialization=seatatransport.compressor=nonemetrics.enabled=falsemetrics.registryType=compactmetrics.exporterList=prometheusmetrics.exporterPrometheusPort=9898

(2)将seata-1.4.2\script\config-center\nacos下文件复制到 seata-server-1.4.2\bin目录下
(3)修改seata-server-1.4.2\conf\file.conf

## transaction log store, only used in seata-serverstore {  ## store mode: file、db、redis  mode = "db"  ## rsa decryption public key  publicKey = ""  ## file store property  file {    ## store location dir    dir = "sessionStore"    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions    maxBranchSessionSize = 16384    # globe session size , if exceeded throws exceptions    maxGlobalSessionSize = 512    # file buffer size , if exceeded allocate new buffer    fileWriteBufferCacheSize = 16384    # when recover batch read size    sessionReloadReadSize = 100    # async, sync    flushDiskMode = async  }  ## database store property  db {    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.    datasource = "druid"    ## mysql/oracle/postgresql/h2/oceanbase etc.    dbType = "mysql"    driverClassName = "com.mysql.cj.jdbc.Driver"    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param    url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai"    user = "root"    password = "root"    minConn = 5    maxConn = 100    globalTable = "global_table"    branchTable = "branch_table"    lockTable = "lock_table"    queryLimit = 100    maxWait = 5000  }  ## redis store property  redis {    ## redis mode: single、sentinel    mode = "single"    ## single mode property    single {      host = "127.0.0.1"      port = "6379"    }    ## sentinel mode property    sentinel {      masterName = ""      ## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"      sentinelHosts = ""    }    password = ""    database = "0"    minConn = 1    maxConn = 10    maxTotal = 100    queryLimit = 100  }}

(4)修改seata-server-1.4.2\conf\register.conf

registry {  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa  type = "nacos"  nacos {    application = "seata-server"    serverAddr = "127.0.0.1:8848"    group = "SEATA_GROUP"    namespace = ""    cluster = "default"    username = "nacos"    password = "nacos"  } }config {  # file、nacos 、apollo、zk、consul、etcd3  type = "file"  nacos {    serverAddr = "127.0.0.1:8848"    namespace = ""    group = "SEATA_GROUP"    username = ""    password = ""    dataId = "seataServer.properties"  }  file {    name = "file.conf"  }}

(5)启动nacos,运行seata-server.bat

3.增加相关表结构

其中t00_user是测试用的表结构

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,  PRIMARY KEY (`id`),  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;--全局事务表--CREATE TABLE IF NOT EXISTS `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`)    ) ENGINE = INNODB    DEFAULT CHARSET = utf8; -- 分支表CREATE TABLE IF NOT EXISTS `branch_table`(    `branch_id`         BIGINT       NOT NULL,    `xid`               VARCHAR(128) NOT NULL,    `transaction_id`    BIGINT,    `resource_group_id` VARCHAR(32),    `resource_id`       VARCHAR(256),    `branch_type`       VARCHAR(8),    `status`            TINYINT,    `client_id`         VARCHAR(64),    `application_data`  VARCHAR(2000),    `gmt_create`        DATETIME(6),    `gmt_modified`      DATETIME(6),    PRIMARY KEY (`branch_id`),    KEY `idx_xid` (`xid`)    ) ENGINE = INNODB    DEFAULT CHARSET = utf8; -- 锁定表CREATE TABLE IF NOT EXISTS `lock_table`(    `row_key`        VARCHAR(128) NOT NULL,    `xid`            VARCHAR(128),    `transaction_id` BIGINT,    `branch_id`      BIGINT       NOT NULL,    `resource_id`    VARCHAR(256),    `table_name`     VARCHAR(32),    `pk`             VARCHAR(36),    `gmt_create`     DATETIME,    `gmt_modified`   DATETIME,    PRIMARY KEY (`row_key`),    KEY `idx_branch_id` (`branch_id`)    ) ENGINE = INNODB    DEFAULT CHARSET = utf8; --seata新版本加的锁表CREATE TABLE IF NOT EXISTS `distributed_lock`(    `lock_key`       CHAR(20) NOT NULL,    `lock_value`     VARCHAR(20) NOT NULL,    `expire`         BIGINT,    PRIMARY KEY (`lock_key`)    ) ENGINE = INNODB    DEFAULT CHARSET = utf8mb4; INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);CREATE TABLE `t00_user`  (  `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,  `user_name` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,  PRIMARY KEY (`user_id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

4.代码配置

我这里没有使用多个服务,简单的使用了单个服务看回滚能不能成功。
项目结构如下:
在这里插入图片描述

(1)pom.xml

                            com.alibaba.cloud            spring-cloud-starter-alibaba-seata            2021.1                                           mysql            mysql-connector-java            8.0.31                                    org.mybatis.spring.boot            mybatis-spring-boot-starter            2.1.0                            org.mybatis            mybatis-spring            1.3.2            compile            

(2)bootstrap.yml

spring:  #允许循环依赖  main:    allow-circular-references: true  application:    name: seata-service  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    #不加allowPublicKeyRetrieval=true会报SQLNonTransientConnectionException    url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true    username: root    password: rootseata:  application-id: seata-server  tx-service-group: tx_service_default_group  service:    vgroup-mapping:      tx_service_default_group: defaultmybatis:  #扫描mapper文件,Mapper文件一般放到resource下,如果放在java目录下,需要在pom文件的bulid标签下将.xml放到include下  mapper-locations: classpath:mapping/*Mapper.xml

(3)测试的相关代码
SeataController .java

package com.example.seata.controller;import com.example.seata.entity.User;import com.example.seata.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;@Controllerpublic class SeataController {    @Autowired    private UserService userService;    @RequestMapping(value = "/test")    @ResponseBody    public String test() {        return "Sentinel server";    }    @RequestMapping(value="/getUser")    @ResponseBody    public List getUser(){        List user = userService.getUser();        return user;    }    @RequestMapping(value="/addUser")    @ResponseBody    public List addUser(){        userService.addUser();        List user = userService.getUser();        return user;    }}

UserDAO.java

package com.example.seata.dao;import com.example.seata.entity.User;import java.util.List;public interface UserDAO {    List getUser();    void addUser(User user);}

User.java

package com.example.seata.entity;public class User {    private String user_id;    private String user_name;    public String getUser_id() {        return user_id;    }    public void setUser_id(String user_id) {        this.user_id = user_id;    }    public String getUser_name() {        return user_name;    }    public void setUser_name(String user_name) {        this.user_name = user_name;    }    public User(String user_id, String user_name) {        this.user_id = user_id;        this.user_name = user_name;    }}

UserServiceImpl.java

package com.example.seata.service.impl;import com.example.seata.dao.UserDAO;import com.example.seata.entity.User;import com.example.seata.service.UserService;import io.seata.spring.annotation.GlobalTransactional;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Service("userService")public class UserServiceImpl implements UserService {    @Autowired    private UserDAO userDAO;    @Override    public List getUser() {        return userDAO.getUser();    }    @Override    @GlobalTransactional(rollbackFor = Exception.class)    public void addUser() {        User user1 = new User("338","小王");        User user2 = new User("339","大力");        User user3 = new User("339","小毛");        userDAO.addUser(user1);        userDAO.addUser(user2);        //加这一行是为了看undo_log,因为异常之后事务结束,undo_log会被删除        try {            new Thread().sleep(5000);        } catch (InterruptedException e) {            throw new RuntimeException(e);        }        userDAO.addUser(user3);    }}

UserService .java

package com.example.seata.service;import com.example.seata.entity.User;import java.util.List;public interface UserService {    public List getUser();    void addUser();}

SeataApplication.java

package com.example.seata;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import java.util.Arrays;@SpringBootApplication@MapperScan( "com.example.seata.dao")//使用MapperScan批量扫描所有的Mapper接口;public class SeataApplication {    public static void main(String[] args) {        args = Arrays.copyOf(args,args.length + 1);        args[args.length - 1] = "--spring.cloud.bootstrap.enabled=true";        SpringApplication.run(SeataApplication.class, args);    }}

UserMapper.xml

        

(4)启动项目
进入http://localhost:8080/addUser
发现报错,并且数据没有增加一条
如果将UserServiceImpl中的@GlobalTransactional(rollbackFor = Exception.class)去掉,会发现数据新增了两条。
说明注解使用成功,回归成功了
我这里为了看undo_log,在代码抛出异常前加上了Sleep5秒的操作,在这五秒内可以看到undo_log的情况
在这里插入图片描述

三、常见报错解决

Failed to get available servers: endpoint format should like ip:port
config.txt的service.vgroupMapping.tx_service_default_group=default中tx_service_default_group
与yml中的tx_service_default_group要一致
2.seata启动闪退/报错
修改seata-server.bat,这样就能看到报错信息了
if “%FORCE_EXIT_ON_ERROR%” == “on” (
if %ERROR_CODE% NEQ 0 exit %ERROR_CODE%
)
cmd
exit /B %ERROR_CODE%
3.数据库连接不上
mysql5.+使用 driverClassName = “com.mysql.jdbc.Driver”
mysql8使用 driverClassName = “com.mysql.cj.jdbc.Driver”

来源地址:https://blog.csdn.net/tttalk/article/details/128483512

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯