SpringCloudAlibaba第三章(分布式事务seata)

目录

1:什么是seata

2:seata安装

3:seata的三种存储模式

3.1:file模式

3.2:DB模式

3.2:redis模式(没有写)

4:seata实现分布式事务的4种模式

5:seata的AT模式

5.1:AT模式模式实现机制

5.2:AT模式的案例实现

5.3:AT模式的优缺点

6:seata的TCC模式

6.1:TCC模式模式实现机制

6.2:TCC模式的案例实现

6.3:TCC模式的优缺点


1:什么是seata

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

官网:http:seata.io/zh-cn/

2:seata安装

下载
https://seata.io/zh-cn/blog/download.html

Seata-Server安装
解压seata-server.zip,启动seata-server.sh

3:seata的三种存储模式

因为seata需要对全局事务和分支事务进行管理存储,所以有三种存储模式

分别是file模式,DB模式,redis模式

3.1:file模式

file会将事务数据存储到本地文件夹 

1:修改conf下的application.yml

2:启动seata-server.sh

到bin目录下看到本地存储的sessionStore文件夹,证明了本地存储

3:访问 http://localhost:7091/#/transaction/list

账户密码都是seata  进入seata控制台

3.2:DB模式

DB会将事务数据存储到自己设置的数据库

1:修改conf下的application.yml

seata:
  #配置中心(在db模式下使用nacos集群或者单机)
  config:
    # support: nacos, consul, apollo, zk, etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8851,127.0.0.1:8853,127.0.0.1:8855
      namespace:
      group: SEATA_GROUP
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      data-id: seataServer.properties

  #注册中心(在db模式下使用nacos集群或者单机)
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8851,127.0.0.1:8853,127.0.0.1:8855
      group: SEATA_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:

  #存储方式(去application.example.yml文件中复制过来)
  store:
    # support: file(适用于单机测试) 、 db(数据库保存事务信息) 、 redis(redis保存事务信息,性能略强于db)
    mode: db
    session:
      mode: db
    lock:
      mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/Seata?useUnicode=true&characterEncoding=utf-8&useSSL=false
      user: root
      password: 123456
      min-conn: 10
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 1000
      max-wait: 5000

2:然后找到建表语句在mysql的Seata库中建表

在本机的数据库Seata下边执行建表sql会得到4张表

3:然后配置nacos的配置文件

配置文件的group和data-id如上边的yml配置,配置文件的内容位置是

seata/script/config-center/config.txt ,需要对文件进行数据源的配置,然后在nacos中创建配置文件

配置文件内容型如下:

#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
#store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
#store.file.dir=file_store/data
#store.file.maxBranchSessionSize=16384
#store.file.maxGlobalSessionSize=512
#store.file.fileWriteBufferCacheSize=16384
#store.file.flushDiskMode=async
#store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://localhost:3306/Seata?useUnicode=true&characterEncoding=utf-8&useSSL=false
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
#store.redis.mode=single
#store.redis.single.host=127.0.0.1
#store.redis.single.port=6379
#store.redis.sentinel.masterName=
#store.redis.sentinel.sentinelHosts=
#store.redis.maxConn=10
#store.redis.minConn=1
#store.redis.maxTotal=100
#store.redis.database=0
#store.redis.password=
#store.redis.queryLimit=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

4:启动seata-server.sh -m db    以数据库存储方式启动

可以在nacos中看到 seata-server的注册服务 

5:访问 http://localhost:7091/#/transaction/list

账户密码都是seata  进入seata控制台

3.2:redis模式(没有写)

redis会将事务数据存储到自己设置的redis,跟上边的相似的配置

4:seata实现分布式事务的4种模式

seata的框架结构如下

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

seata实现分布式事务的4种模式

1:AT模式(常用)

2:TCC模式(常用)

3:SAGA模式(不常用,链式调用,适用于不同的老系统,有补偿机制,代码复杂)

4:XA模式(不常用)

在下边的内容会详细介绍这几种模式 

5:seata的AT模式

详见官网:Seata AT 模式 | Seata

5.1:AT模式模式实现机制

AT模式是两段式提交的演变

        一阶段:业务数据和回滚日志(undo log)在同一个本地事务中需要先拿到本地锁全局锁提交,只会释放本地锁。

        二阶段:commit提交是异步化的,非常快速。rollback是根据一阶段的回滚日志来进行回滚。

无论提交还是回滚都会在这个时候释放全局锁。

AT模式的写隔离和读隔离,这是啥意思呢?详见官网的解释和图片实例,由于解释和图片很多,这里只做简单的解释

写隔离

        两个事务修改同一条数据,需要先竞争获取本地锁(也就是行锁),一个事务一阶段提交的时候需要再获取全局锁,竞争的事务等待,等到人家全局锁二阶段提交后,等待的事务才能再次获取到全局锁,提交自己的事务。保证了就像本地事务一样写操作的安全性也就是写隔离。

读隔离

        我们知道mysql的默认隔离级别的RR(可重复读),能够防止脏读,不可重复读,幻读的情况,但是当我们使用分布式事务seata的时候,他能做到RR的隔离级别吗?答案是不能,seata只能做到在Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。他会读取到别的事务没有提交的数据,发生脏读。

        怎样避免发生脏读,或者更高的安全性?如果应用在特定场景下,必需要求全局的读已提交 ,目前 Seata 的方式是通过 SELECT    FOR UPDATE 语句的代理。

5.2:AT模式的案例实现

我们首先介绍项目情况,9002通过feign调用9003项目,9002新增一条学生表数据,9003新增一条老师表数据,通过seata的AT模式验证分布式事务

 

0:在每一个数据库下边创建undo表,用于数据回滚

CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

1:pom文件配置9002和9003项目的pom文件一样

<!--springboot3.0.3适配的springCloud版本是2022.0.0-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SCA01_Seata_9003</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- 1:springCloud版本和spring-cloud-alibababa版本 很重要 版本必须匹配-->
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2022.0.0</spring-cloud.version>
        <spring-cloud-alibaba.version>2022.0.0.0</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>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 德鲁伊数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        <!-- mybatisPlus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <!--spring-cloud-starter-alibaba服务注册-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--spring-cloud-starter-alibaba配置管理-->
        <!--        <dependency>-->
        <!--            <groupId>com.alibaba.cloud</groupId>-->
        <!--            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
        <!--        </dependency>-->
        <!--sentinel依赖的jar-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>


        <!--seata依赖-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


    </dependencies>

 

2:9002和9003的application.properties也是基本一样

#0:项目信息
server.port=9003
spring.application.name=SCA01-Seata-9003

##1:nacos集群地址 用来注册服务nacos
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8851,127.0.0.1:8853,127.0.0.1:8855
##定义坐标
spring.cloud.nacos.discovery.group=SEATA_GROUP
#端点监控
management.endpoints.web.exposure.include=*

#2:seata分布式事务配置 default_tx_group=default 来源于db模式的seata配置文件
seata.enabled=true
seata.service.vgroup-mapping.default_tx_group=default
seata.service.disable-global-transaction=false
seata.service.grouplist.default=127.0.0.1:8091

#3:注册sentinel监控地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
spring.cloud.sentinel.transport.port=8719
#启动就会加载
spring.cloud.sentinel.eager=true

#4:德鲁伊数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/Seata3?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=10
spring.datasource.druid.max-wait=4000
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

 

3:9002核心代码

   //9002的controller代码用于外部请求
    @GetMapping(value = "add")
    public List<Student> add(Student student){
        return studentService.add(student);
    }


    //9002的全局事务代码
    @GlobalTransactional
    @Override
    public List<Student> add(Student student) {
        //9002项目添加学生表信息
        studentMapper.insert(student);
        //这里模拟报错,9002已经第一阶段提交
        //int a = 9002 / 0;

        //feign调用9003项目添加老师表信息
        Teacher teacher = new Teacher();
        teacher.setName("测试老师22");
        teacher.setAddress("洛阳");
        feignService9003.add(teacher);

        //这里模拟报错,9002和9003都已经第一阶段提交,第二阶段需要回滚 debug断点
        //可以在数据库的看到学生表和老师表存在了新增的数据,并且undo日志也存在
        //这就是seata的第一阶段提交,读未提交(Read Uncommitted) 其他的事务会发生脏读
        int a=9002/0;
        return studentMapper.selectList(null);
    }


//远程调用nacos上的SCA01_Seata_9003服务
@FeignClient(value = "SCA01-Seata-9003")
public interface FeignService9003 {

    @GetMapping(value = "selectById")
    Teacher selectById(@RequestParam(value = "id") int id);

    @GetMapping(value = "selectAll")
    List<Teacher> selectByALL();

    @GetMapping(value = "deleteById")
    int deleteByID(@RequestParam(value = "id") int id);


    @PostMapping(value = "add")
    boolean add(@RequestBody Teacher teacher);

}

4:9003核心代码 

 @PostMapping(value = "add")
    public boolean add(@RequestBody Teacher teacher){
        boolean save = teacherService.save(teacher);
        //9003模拟报错验证全局事务
        //int a=9003/0;
        return save;
    }

5:代码测试,断点验证,断点打到 int a=9002/0;

http://localhost:9002/add?name=学生张三22&address=北京1&score=88

 

断点接着走会报错,因为int a=9002/0 会报异常,数据能回滚吗? 我们再次查询两个表发现数据都不在了,根据undo日志回滚了。这就验证了分布式事务生效了,你也可以在其他的地方验证,都会生效,这里不过多举例。

读隔离验证:这里有个问题,seata修改数据会发生锁竞争,是安全的。但是修改的数据会在数据库被我们查到,会发生读已提交(其他事务读取到本事务没有提交的数据),我们怎么保证安全呢?

 

读隔离验证:我们可以在可以在9003代码中加入 for update

//9003的controller
 @GetMapping(value = "selectAll")
    public List<Teacher> selectByALL() throws InterruptedException {
        return teacherService.selectList();
    }

//9003的service 加入GlobalTransactional
 @GlobalTransactional
    @Override
    public List<Teacher> selectList() {
        return teacherMapper.selectList();
    }

//9003的mapper 假如for update
 <select id="selectList" resultType="com.example.SCA01_Seata_9003.entity.Teacher">
        select * from Teacher for update
    </select>

读隔离:再次验证,读已提交(Read committed) 

 

5.3:AT模式的优缺点

两段式提交的变种,第一阶段就会提交本地事务,数据能查询到,第二阶段异步提交、回滚

写隔离安全

读隔离在msyql数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

6:seata的TCC模式

详见官网

Seata TCC 模式 | Seata

6.1:TCC模式模式实现机制

TCC也是两阶段提交的演变,整体是 两阶段提交 的模型。

全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

        一阶段: prepare 行为

        二阶段: commit 或 rollback 行为

TCC和AT模式对比

AT 模式基于 支持本地 ACID 事务 的 关系型数据库:(不需要额外代码,只需要在配置和注解)

        一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。

        二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。

        二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:(也就需要是自己写代码)

        一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。

        二阶段 commit 行为:调用 自定义 的 commit 逻辑。

        二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

6.2:TCC模式的案例实现

跟前边相似,9022通过feign调用9023

0:删除数据库下的undo表 

1:pom文件配置9002和9003项目的pom文件一样

2:9022和9023的application.properties也是上边一样

 

3:9022核心代码

 //9022的controller代码方法,用于外部请求
    @GetMapping(value = "add")
    @GlobalTransactional //TCC模式注解必须加到外部
    public List<Student> add(Student student)
    {
        System.out.println("进入TCC业务测试");
        return studentService.addStudent(student);
    }



//service接口代码
@LocalTCC
public interface StudentService extends IService<Student> {

    //两阶段的业务操作name就是一个动态的bean
    @TwoPhaseBusinessAction(name = "addStudent",
            commitMethod = "addStudent_commit",
            rollbackMethod = "addStudent_rollback")
    List<Student> addStudent(@BusinessActionContextParameter(value ="student") Student student);

    //TCC的CommitMethod 手动提交 BusinessActionContext是map key就是student
    Boolean addStudent_commit(BusinessActionContext businessActionContext);

    //TCC的RollbackMethod 手动回滚
    Boolean addStudent_rollback(BusinessActionContext businessActionContext) throws JsonProcessingException;

}



//service接口实现类代码
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student>
        implements StudentService {

    @Autowired
    StudentMapper studentMapper;

    @Autowired
    FeignService9023 feignService9023;


    //9022的TCC全局事务代码
    //@GlobalTransactional 这里不加注解  注解加到外边
    @Override
    public List<Student> addStudent(Student student) {
        //9002项目添加学生表信息
        studentMapper.insert(student);
        System.out.println("插入学生表信息:" + student);
        //这里模拟TCC报错,9022已经第一阶段提交
        //int a = 9022 / 0;

        //feign调用9003项目添加老师表信息
        Teacher teacher = new Teacher();
        teacher.setName("测试老师22");
        teacher.setAddress("洛阳");
        feignService9023.add(teacher);

        //这里模拟报错,9022和9023都已经第一阶段提交,第二阶段需要回滚 debug断点
        //可以在数据库的看到学生表和老师表存在了新增的数据,并且undo日志也存在
        //这就是seata的第一阶段提交,读未提交(Read Uncommitted) 其他的事务会发生脏读
        int a = 9022 / 0;
        return studentMapper.selectList(null);
    }

    @Override
    public Boolean addStudent_commit(BusinessActionContext businessActionContext) {
        System.out.println("=====9022的addStudent的提交方法");
        return true;
    }

    @Override
    //回滚需要手动操作回滚
    public Boolean addStudent_rollback(BusinessActionContext businessActionContext) throws JsonProcessingException {
        System.out.println("=====9022的addStudent的回滚方法");
        String studentJson = businessActionContext.getActionContext("student").toString();

        ObjectMapper objectMapper = new ObjectMapper();
        Student student = objectMapper.readValue(studentJson, Student.class);

        Map map = new HashMap();
        map.put("name", student.getName());
        map.put("address", student.getAddress());
        studentMapper.deleteByMap(map);
        return true;
    }
}



//feign的远程调用nacos上的SCA01_Seata_9023服务
@FeignClient(value = "SCA01-SeataTCC-9023")
public interface FeignService9023 {

    @GetMapping(value = "selectById")
    Teacher selectById(@RequestParam(value = "id") int id);

    @GetMapping(value = "selectAll")
    List<Teacher> selectByALL();

    @GetMapping(value = "deleteById")
    int deleteByID(@RequestParam(value = "id") int id);


    @PostMapping(value = "add")
    boolean add(@RequestBody Teacher teacher);
}

4:9023核心代码,提供给9022通过feign调用

//9023的controller方法 给feign调用
 @PostMapping(value = "add")
    public boolean add(@RequestBody Teacher teacher){
         teacherService.save1(teacher);
        //9003模拟报错验证全局事务
        //int a=9003/0;
        return true;
    }



//9023的TeacherService接口  需要注解@LocalTCC
@LocalTCC
public interface TeacherService extends IService<Teacher> {

    List<Teacher> selectList();

    //两段式提交
    @TwoPhaseBusinessAction(name = "save1",
            commitMethod = "save1_commit",
            rollbackMethod = "save1_rollback")
    int save1(@BusinessActionContextParameter(paramName = "teacher") Teacher teacher);

    boolean save1_commit(BusinessActionContext actionContext);

    boolean save1_rollback(BusinessActionContext actionContext);
}

//TeacherService接口的实现类 方法
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher>
        implements TeacherService {

    @Autowired
    TeacherMapper teacherMapper;
    private int teacher_id;

    @Override
    public List<Teacher> selectList() {
        return teacherMapper.selectList();
    }

    @Override
    //准备方法
    public int save1(Teacher teacher) {
        int insert = teacherMapper.insert(teacher);
        Integer id = teacher.getId();
        this.teacher_id=id;
        return insert;
    }

    @Override
    //提交方法
    public boolean save1_commit(BusinessActionContext actionContext) {
        System.out.println("=====9023的addTeacher的提交方法");
        return true;
    }

    @Override
    //回滚方法
    public boolean save1_rollback(BusinessActionContext actionContext) {
        System.out.println("=====9023的addTeacher的回滚方法");
        teacherMapper.deleteById(teacher_id);
        return true;
    }


}

 5:debug测试因为 断点在9022的int a = 9022 / 0;

http://localhost:9022/add?name=tcc3&address=纽约

在此停顿观察数据库

 然后int a = 9022 / 0; 放行后在查询数据库,发现插入的两条数据会回滚,走了各自的回滚方法

 这里验证的TCC模式能够保证 分布式事务的一致性。

在int a = 9022 / 0没有放行的时候,我们接着查询9023的数据,回出现读未提交,

所以想要保证安全,防止脏读?

跟AT模式一样这里的读也需要添加select for update,并且加全局注解能保证安全吗?

答案是不能,因为TCC模式是通过手动代码删除或者修改数据的,不能保证脏读。慎重使用TCC,存在业务限制。

6.3:TCC模式的优缺点

缺点:

1:需要自己手写代码的准备,提交,回滚方法,代码耦合度高

2:跟AT模式一样这里的读也需要添加select for update,并且加全局注解能保证安全吗?

        答案是不能,因为TCC模式是通过手动代码删除或者修改自己添加和修改的数据,不能保证脏读。慎重使用TCC,存在业务限制。

优点:

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值