SpringCloud学习(13)-SpringCloudAlibaba-Seata

一、介绍:

Seata是阿里巴巴旗下的产品,是一款开源的分布式事务解决方案,旨在解决分布式事务问题。

我们有必要先了解一下分布式事务:

在微服务体系中,每一个模块都有连接一个数据库,这一点与单体项目是不同的,单体项目就连接一个数据库。

那么如果有多个模块之间相互调用,怎样保证各个模块之间的事务一致性?由此引出了分布式事务。

在同一个数据库中,我们要保证事物的一致性是很简单的。因为MySQL是基于单机事物的,所以一旦遇到跨库的场景,那么MySQL数据库就无能为力了。在这种情景下,seata蕴育而生。

二、Seata的下载和启动:

seata的官网:Apache Seata

seata的下载地址:

https://github.com/apache/incubator-seata/releases/download/v2.0.0/seata-server-2.0.0.zip

e4ea6f2337d549fe95bae2709f3d508a.png

有三个概念要先了解:

  1. TC是事务协调器(就是Seata本身),负责管理全局事务的执行过程。它生成全局唯一的事务ID,并协调各个分支事务的提交或回滚,以确保数据的一致性。有且仅有一个

  2. TM是事务管理器,(标注全局@GlobalTransactional启动入口动作的微服务模块)负责发起全局事务的提交或回滚请求,并与TC交互以执行相应操作。它与业务逻辑代码交互,触发分支事务的执行,并根据TC的指示来决定全局事务的最终状。有且仅有一个

  3. .RM是资源管理器,(各个模块的MySQL数据库本身)负责管理分支事务的资源,如数据库、消息队列、缓存等。它接收TC的指令并执行相应的事务操作,以确保分支事务的一致性。可以有多个

总的来说,TC负责全局事务的协调和管理,TM负责全局事务的发起和控制,RM负责具体资源的管理和事务操作。它们共同协作,实现了Seata分布式事务框架的核心功能,确保分布式系统中事务的一致性和可靠性。

下载完seata之后,要使用seata要现在本地的MySQL数据库中新建一个数据库seata,来记录seata在运行过程中的信息;

CREATE DATABASE seata;USE seata;

sql脚本:
 

create table branch_table
(
    branch_id         bigint        not null
        primary key,
    xid               varchar(128)  not null,
    transaction_id    bigint        null,
    resource_group_id varchar(32)   null,
    resource_id       varchar(256)  null,
    branch_type       varchar(8)    null,
    status            tinyint       null,
    client_id         varchar(64)   null,
    application_data  varchar(2000) null,
    gmt_create        datetime(6)   null,
    gmt_modified      datetime(6)   null
);

create index idx_xid
    on branch_table (xid);


create table distributed_lock
(
    lock_key   char(20)    not null
        primary key,
    lock_value varchar(20) not null,
    expire     bigint      null
);

INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO seata.distributed_lock (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);



create table global_table
(
    xid                       varchar(128)  not null
        primary key,
    transaction_id            bigint        null,
    status                    tinyint       not null,
    application_id            varchar(32)   null,
    transaction_service_group varchar(32)   null,
    transaction_name          varchar(128)  null,
    timeout                   int           null,
    begin_time                bigint        null,
    application_data          varchar(2000) null,
    gmt_create                datetime      null,
    gmt_modified              datetime      null
);

create index idx_status_gmt_modified
    on global_table (status, gmt_modified);

create index idx_transaction_id
    on global_table (transaction_id);


create table lock_table
(
    row_key        varchar(128)      not null
        primary key,
    xid            varchar(128)      null,
    transaction_id bigint            null,
    branch_id      bigint            not null,
    resource_id    varchar(256)      null,
    table_name     varchar(32)       null,
    pk             varchar(36)       null,
    status         tinyint default 0 not null comment '0:locked ,1:rollbacking',
    gmt_create     datetime          null,
    gmt_modified   datetime          null
);

create index idx_branch_id
    on lock_table (branch_id);

create index idx_status
    on lock_table (status);

create index idx_xid
    on lock_table (xid);



创建好库和表之后,更改seata的内容来启动seata:
在seata的conf文件夹下,更改application.yml文件,启动seata:

#  Copyright 1999-2019 Seata.io Group.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
 
server:
  port: 7091
 
spring:
  application:
    name: seata-server
 
logging:
  config: classpath:logback-spring.xml
  file:
    path: ${log.home:${user.home}/logs/seata}
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash
 
console:
  user:
    username: seata
    password: seata
seata:
# 配置方式nacos
  config:
    type: nacos
    # support: nacos, consul, apollo, zk, etcd3
    
    nacos:
        server-addr: localhost:8848
        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
# 注册方式 nacos
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    type: nacos
    # preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: localhost:8848
      group: SEATA_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos
# 存储方式 db mysql
  store:
    mode: db
    # support: file 、 db 、 redis 、 raft
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
      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
  #  server:
  #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**

根据你nacos、MySQL表的信息,进行相应的更改;

先启动nacos、再启动seata;(seata的启动在bin下的seata-server.bat)

可以看到seata已经作为一个模块注册进了nacos中;

访问7091端口,可以看到seata的图形化界面(用户名和密码都是seata)

三、Seata测试Demo(AT模式)


业务说明:订单服务→库存服务→支付服务:下订单 --->减库存---->扣余额---->修改(订单)状态

maven依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

配置文件application(seata相关):

seata:
  registry:
    type: nacos
    nacos:
      server-addr: localhost:8848
      namespace: ""
      group: SEATA_GROUP
      application: seata-server
  tx-service-group: default_tx_group #事务组,由他获得TC服务的集群名称
  service:
    vgroup-mapping: #源码默认名字 default_tx_group default
      default_tx_group: default #事务组与TC服务集群的映射关系
  data-source-proxy-mode: AT  #默认是AT 如果是AT可不写

logging:
  level:
    io:
      seata: info

主要业务方法:

其中int i =10/0;用于测试异常情况

@Override
    @GlobalTransactional(name = "create-order-transaction", rollbackFor = Exception.class)
    public void create(Order order) {
        // xid全局事务检查
        String xid = RootContext.getXID();

        // 1. 新建订单
        log.info("-------------> 开始新建订单, XID: {}", xid);
        order.setStatus(0);
        int result = orderMapper.insertSelective(order);

        Order orderFromDB;
        if (result > 0) {
            orderFromDB = orderMapper.selectOne(order);
            log.info("-------------> 新建订单成功, OrderInfo: {}", orderFromDB);

            // 2. 扣减库存
            log.info("-------------> 开始扣减库存");
            storageFeignApi.decrease(orderFromDB.getProductId(), orderFromDB.getCount());
            log.info("-------------> 扣减库存成功");

            // 3. 扣减账户余额
            log.info("-------------> 开始扣减余额");
            accountFeignApi.decrease(order.getUserId(), order.getMoney());
            log.info("-------------> 扣余额存成功");
            int i = 10/0;
            // 4. 修改订单状态
            log.info("-------------> 开始修改订单状态");
            Example whereCondition = new Example(Order.class);
            Example.Criteria criteria = whereCondition.createCriteria();
            criteria.andEqualTo("id", orderFromDB.getId());
            criteria.andEqualTo("status", 0);
            orderFromDB.setStatus(1);
            int updateResult = orderMapper.updateByExampleSelective(orderFromDB, whereCondition);
            log.info("-------------> 修改订单状态成功");


        }

        log.info("-------------> 结束新建订单, XID: {}", xid);
    }

调用接口前三个数据库,三张表以及对应数据库undo_log表如下:

执行过程中(断点在异常语句处)数据库及seata控制台:

执行完成:

结果:可以看到在过程中记录添加进数据库并进行了相关记录,异常后进行了回滚。

四、总结:

我们之前的步骤都是建立在seata的AT模式上;

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。

整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

在一阶段,Seata 会拦截“业务 SQL”,

1  解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,

2  执行“业务 SQL”更新业务数据,在业务数据更新之后,

3  其保存成“after image”,最后生成行锁。

以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

1d9682c3d0764438b93d426675f8e726.png

二阶段分为两种情况:

1、正常提交:

二阶段如是顺利提交的话,

因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

81310ceb73094a0e8fd59a286b9f78c3.png

2、异常回滚:

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。

回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,

如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

a48275f6955343c99d6d217a9af2148a.png

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值