【分布式事务】02--Seata AT模式

一,Seata Server - TC全局事务协调器

Seata AT 事务原理 《分布式事务(三)Seata分布式事务框架-AT模式介绍》,介绍了 AT 事务的三个角色:TC(事务协调器)、TM(事务管理器)和RM(资源管理器),其中 TM 和 RM 是嵌入在业务应用中的,而 TC 则是一个独立服务。
在这里插入图片描述
Seata Server 就是 TC,直接从官方仓库下载启动即可,下载地址:
官方地址

二,Seata Server 配置

Seata Server 的配置文件有两个:

  • seata/conf/registry.conf – 向注册中心注册
  • seata/conf/file.conf – 协调器运行过程中记录的日志数据,要存到数据库
  • seata/bin/seata-server.bat – 使用的内存默认2G,测试环境把内存改小:256m

1.registry.conf

在这里插入图片描述
Seata Server 要向注册中心进行注册,这样,其他服务就可以通过注册中心去发现 Seata Server,与 Seata Server 进行通信。
Seata 支持多款注册中心服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。
Seata Server 要向注册中心进行注册,这样,其他服务就可以通过注册中心去发现 Seata Server,与 Seata Server 进行通信。
Seata 支持多款注册中心服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 这里选择 eureka 注册配置
  type = "eureka"

  nacos {
	......
  }

  # eureka的注册配置
  eureka {
    # 注册中心地址
    serviceUrl = "http://localhost:8761/eureka"
    # 注册的服务ID
    application = "seata-server"
    weight = "1"
  }
  
  redis {
	......
  }
  ......

2.file.conf

Seata 需要存储全局事务信息、分支事务信息、全局锁信息,这些数据存储到什么位置?
针对存储位置的配置,支持放在配置中心,或者也可以放在本地文件。Seata Server 支持的配置中心服务有:nacos 、apollo、zk、consul、etcd3。
file.conf 中对事务信息的存储位置进行配置,存储位置支持:file、db、redis。
这里我们选择数据库作为存储位置,这需要在 file.conf 中进行配置:

store {
  ## store mode: file、db、redis
  # 这里选择数据库存储
  mode = "db"

  ## file store property
  file {
  	......
  }

  # 数据库存储
  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.jdbc.Driver"

	# 数据库连接配置
    url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 30

	# 事务日志表表名设置
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"

    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
  	......
  }
}

在这里插入图片描述

3.seata-server.bat

在这里插入图片描述

三,启动 Seata Server

双击 seata-server.bat 启动 Seata Server。
注意:这里jdk文件只能是1.8的版本
在这里插入图片描述
查看 Eureka 注册中心 Seata Server 的注册信息:
在这里插入图片描述
注意:
这里不能选中,否则会连接超时
在这里插入图片描述
右键取消选中
在这里插入图片描述

四,在父项目order-parent添加seata依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-seata</artifactId>
    <version>${spring-cloud-alibaba-seata.version}</version>
    <exclusions>
    	<exclusion>
         	<artifactId>seata-all</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>${seata.version}</version>
</dependency>

五,订单模块(order)添加Seata AT事务

在这里插入图片描述
订单调用库存和账户,我们先从前面的订单开始。
在订单项目中要启动全局事务,还要执行订单保存的分支事务。

1.配置application.yml

spring:
  application:
    name: order
  datasource:
    url: jdbc:mysql:///seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    jdbcUrl: ${spring.datasource.url}
  cloud:
    alibaba:
      seata:
        tx-service-group: order_tx_group #当前模块在哪个事务组中
# account 8081 storage 8082 order 8083
server:
  port: 8083
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.drhj.order.entity
  configuration:
    map-underscore-to-camel-case: true  #驼峰命名
logging:                #打印日志
  level:
    com.drhj.order.mapper: debug
ribbon:
  MaxAutoRetriesNextServer: 0 #关闭重试

2.配置registry.conf

需要从注册中心获得 TC 的地址,这里配置注册中心的地址。
TC 在注册中心注册的服务ID在下面 file.conf 中指定。

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    # application = "default"
    # weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    cluster = "default"
    timeout = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

3.file.conf

在这里我们指定 TC 的服务ID seata-server:
vgroupMapping.order_tx_group = “seata-server”
order_tx_group 对应 application.yml 中注册的事务组名。

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  # order_tx_group 与 yml 中的 “tx-service-group: order_tx_group” 配置一致
  # “seata-server” 与 TC 服务器的注册名一致
  # 从eureka获取seata-server的地址,再向seata-server注册自己,设置group
  vgroupMapping.order_tx_group = "seata-server"
  #only support when registry.type=file, please don't set multiple addresses
  order_tx_group.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

4.创建 seata 数据源代理

Seata AT 事务对业务代码无侵入,全自动化处理全局事务,其功能是靠 Seata 的数据源代理工具实现的。
这里我们创建 Seata 的数据源代理,并排除 Spring 默认的数据源。

package com.drhj.order;

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DSPAutoConfiguration {
    //新建原始数据源对象
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
    //新建数据源代理对象
    @Primary //首选对象,返回值相同
    @Bean
    public DataSource dataSourceProxy(DataSource ds) {
        return new DataSourceProxy(ds);
    }
}

主程序中排除Springboot 的默认数据源:

package com.drhj.order;

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 org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@MapperScan("com.drhj.order.mapper")
//禁用spring默认的数据源配置
//只使用自定义配置 DSPAutoConfiguration
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class OrderApplication {

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

5.启动全局事务

Seata AT 对业务无侵入,所以启动全局事务非常简单,只需要添加一个 @GlobalTransactional 注解即可。
另外我们一步一步地添加全局事务并测试,这里先把 storage 和 account 调用注掉。

package com.drhj.order.service;

import com.drhj.order.entity.Order;
import com.drhj.order.feign.AccountClient;
import com.drhj.order.feign.EasyIdClient;
import com.drhj.order.feign.StorageClient;
import com.drhj.order.mapper.OrderMapper;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private EasyIdClient easyIdClient;
    @Autowired
    private AccountClient accountClient;
    @Autowired
    private StorageClient storageClient;

    @GlobalTransactional //启动全局事务,只在第一个模块上添加
    @Transactional
    @Override
    public void create(Order order) {
        //远程调用发号器,生成订单id
        String s = easyIdClient.nextId("order_business");
        Long id = Long.valueOf(s);
        order.setId(id);
        orderMapper.create(order);
        //远程调用库存,减少库存
        //storageClient.decrease(order.getProductId(),order.getCount());
        //远程调用账户,扣减账户
        //accountClient.decrease(order.getUserId(),order.getMoney());
    }
}

6.启动 order 项目进行测试

一次启动 Eureka --> Seata Server --> Easy Id Generator --> Order
访问地址:
http://localhost:8083/create?userId=1&productId=1&count=10&money=100
观察控制台,看到全局事务和订单的分支事务已经启动,并可以看到全局事务ID(XID)和分支事务ID(Branch ID):
在这里插入图片描述
然后观察数据库中新添加的订单数据:
在这里插入图片描述

7.accout与storage配置同上

唯一的不同的是业务实现类不需要加全局事务
在这里插入图片描述

六,全局测试

1.成功测试

1)启动db_init 刷新数据库
2)依次启动 Eureka --> Seata Server --> Easy Id Generator --> account --> storage --> Order
3)访问地址:http://localhost:8083/create?userId=1&productId=1&count=10&money=100
订单会调用库存和账户,这三个服务会分别启动一个分支事务,三个分支事务一起组成一个全局事务:

在这里插入图片描述
观察三个项目的控制台都有Seata AT事务的日志及数据库信息
order:
在这里插入图片描述
在这里插入图片描述

account:
在这里插入图片描述
在这里插入图片描述

storage:
在这里插入图片描述
在这里插入图片描述

2.失败测试

1)模拟账户异常
在这里插入图片描述
2)重启账户服务
3)访问地址:http://localhost:8083/create?userId=1&productId=1&count=10&money=100
4)观察三个项目的控制台都有Seata AT事务的日志及数据库信息
order:
在这里插入图片描述
在这里插入图片描述

account:
在这里插入图片描述
在这里插入图片描述

storage:
在这里插入图片描述
在这里插入图片描述
可见,数据库实现了回滚。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值