【十二】SpringCloud整合seata分布式事务 AT模式

一、先启动注册中心

这里使用注册中心eureka 

https://blog.csdn.net/jy02268879/article/details/100176361

二、seata-server服务端启动及其配置 

下载seata-server服务端代码:https://github.com/seata/seata/releases

要跑在Linux上就下载seata-server-1.4.1.tar.gz

我这里是跑在windows上做个测试,所以下的seata-server-1.4.1.zip

下载后可见目录

修改服务端配置:

conf/registry.conf

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 我这里用的注册中心是eureka
  type = "eureka"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }

# 我这里用的注册中心是eureka
# 配置eureka注册中心的地址
# 配置本服务在注册中心中叫什么名字
  eureka {
    serviceUrl = "http://localhost:8100/eureka"
    application = "seata_tc_server"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 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"
  }
}

# 我这里配置中心就是用的file ,配置文件,就是安装包中的file.conf文件
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
    apolloAccesskeySecret = ""
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

conf/file.conf

## transaction log store, only used in seata-server
store {
  # 这里表示seata-server服务端用什么数据库,这里用的mysql
  ## store mode: file、db、redis
  mode = "db"

  ## 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 = "dbcp"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "root"
    minConn = 1
    maxConn = 10
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

  ## redis store property
  redis {
    host = "127.0.0.1"
    port = "6379"
    password = ""
    database = "0"
    minConn = 1
    maxConn = 10
    maxTotal = 100
    queryLimit = 100
  }

}

这里就需要去file.conf的配置文件里面配的mysql中去创建三张表

CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

现在可以启动seata-server客户端了

bin/seata-server.bat

三、客户端代码

代码例子

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

  • 仓储服务:对给定的商品扣除仓储数量。
  • 订单服务:根据采购需求创建订单。
  • 帐户服务:从用户帐户中扣除余额。

注意这里,每个服务是单独的数据库

注意,要用seata就需要在每个服务的数据库中都创建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=21 DEFAULT CHARSET=utf8;

CREATE TABLE `account` (
  `user_id` varchar(50) NOT NULL,
  `money` double DEFAULT NULL COMMENT '余额',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `business_table` (
  `business_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(50) DEFAULT NULL,
  `commodity_code` varchar(50) DEFAULT NULL,
  `order_count` int(11) DEFAULT NULL,
  PRIMARY KEY (`business_id`)
) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8;

CREATE TABLE `order_table` (
  `order_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单号',
  `user_id` varchar(50) DEFAULT NULL,
  `commodity_code` varchar(50) DEFAULT NULL COMMENT '商品代码',
  `order_count` int(11) DEFAULT NULL,
  `order_money` double DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8;

CREATE TABLE `storage` (
  `commodity_code` varchar(50) NOT NULL COMMENT '商品代码',
  `storage_count` int(11) DEFAULT NULL COMMENT '库存数量',
  PRIMARY KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

每一个需要用到seata的服务需要的修改

目录

pom.xml

            <!--seata  todo 如果用2.2.0release 启动后一直报错:string不能转换为boolean service.disableGlobalTransaction-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-seata</artifactId>
                <version>2.1.0.RELEASE</version>
                <exclusions>
                    <exclusion>
                        <artifactId>seata-all</artifactId>
                        <groupId>io.seata</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <artifactId>seata-all</artifactId>
                <groupId>io.seata</groupId>
                <version>1.4.1</version>
            </dependency>

在resource目录下有两个配置文件file.conf  registry.conf

只要需要用到seata的服务,都需要在在即的resource目录下建立者两个文件

registry.conf

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8100/eureka"
    application = "seata_tc_server"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 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
  type = "file"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
    apolloAccesskeySecret = ""
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

file.conf

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 {
#这里注意,等号前后都是配置,前面是yml里配置的事务组,后面是register.conf里定义的seata-server
  vgroupMapping.test_tx_group = "seata_tc_server"
  #only support when registry.type=file, please don't set multiple addresses
  seata_tc_server.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
  }
}

application.yml 只要需要用到seata的服务,都需要在配置这个

###起个名字作为服务名称(该服务注册到eureka注册中心的名称,比如订单服务)
spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: test_tx_group # 定义事务组的名称

启动类里面加上 只要需要用到seata的服务,都需要在配置这个

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

因为这里是用的mybaits所以需要再加个配置类   

package com.sid.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
public class DataSourceProxyConfig {

    @Bean(destroyMethod = "close", initMethod = "init")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }


    @Bean
    @Primary
    public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceProxy);

        /**
         * 如果不自己把mapping的路径设置进去就一直报错
         * ibatis.binding.BindingException: Invalid bound statement (not found)
         * */
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:/mapping/*.xml"));
        factoryBean.setTransactionFactory(new SpringManagedTransactionFactory());

        return factoryBean.getObject();
    }
}

使用

在business的service代码中

在该方法外面加入@GlobalTransactional表示开启全局事务

    /**
     * 采购
     */
    @GlobalTransactional
    @Transactional
    @Override
    public void purchase(String userId, String commodityCode, int orderCount) {

        Business b = new Business();
        b.setUserId(userId);
        b.setCommodityCode(commodityCode);
        b.setOrderCount(orderCount);

        int i = businessMapper.insertSelective(b);

        //远程调用
        String storageDeductResult = storageService.deduct(commodityCode, orderCount);

       //远程调用
        Order order = orderService.create(userId, commodityCode, orderCount);

        String orderCreateResult = order.getMsg();


        if(!storageDeductResult.equals("success") || !orderCreateResult.equals("success")){
            System.out.println("resutl storage service :"+storageDeductResult);

            System.out.println("resutl order service :"+orderCreateResult);

            throw new RuntimeException("BusinessServiceImpl purchase fail");
        }

    }

 在storage的service中

    /**
     * 商品扣库存
     * */
    @Override
    @Transactional
    public String deduct(String commodityCode, int count) {

        Storage storage = storageMapper.selectByPrimaryKey(commodityCode);
        Integer storageCount = storage.getStorageCount();
        storage.setStorageCount(storageCount - count);
        int i = storageMapper.updateByPrimaryKey(storage);
        //int ii = 1/0;
        if(i == 1){
            return "success";
        }
        return "fail";
    }

在order的service中

    /**
     * 创建订单
     */
    @Transactional
    public Order create(String userId, String commodityCode, Integer orderCount) {
        Double orderMoney = Double.valueOf(orderCount);
        Order order = new Order();
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setOrderCount(orderCount);
        order.setOrderMoney(orderMoney);

        orderMapper.insertSelective(order);

        /**
         * 这里的RPC调用是同步的
         * todo 需要写个异步的例子  这个可能需要用dubbo来写哦  feign都是同步个嘛
         * */
        String accountDebitResult = accountService.debit(userId, orderMoney);

        if (!accountDebitResult.equals("success")) {
            System.out.println("result account service :"+accountDebitResult);
            throw new RuntimeException("OrderService create method rpc request accountService debit fail. accountDebitResult:"+accountDebitResult);
        }

        order.setMsg("success");
        return order;
    }

 在account的service中

    /**
     * 扣钱
     * */
    @Override
    @Transactional
    public String debit(String userId, Double money) {
        Account account = accountMapper.selectByPrimaryKey(userId);
        Double money1 = account.getMoney();
        account.setMoney(money1-money);
        int i = accountMapper.updateByPrimaryKey(account);
        if(i == 1){
            return "success";
        }
        return "fail";
    }

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud Alibaba Seata 是一款优秀的分布式事务解决方案,它可以帮助开发人员在分布式环境下处理复杂的事务操作。在分布式系统中,由于多个服务之间存在依赖关系,因此需要对服务之间的事务进行协调和管理,以确保数据的一致性和完整性。以下是对Spring Cloud Alibaba Seata处理分布式事务的需求分析: 1. 事务管理:在分布式系统中,需要对多个服务之间的事务进行管理和协调,以确保数据的一致性和完整性。Spring Cloud Alibaba Seata提供了全局事务管理功能,可以跨多个服务进行事务管理。 2. 分布式事务的隔离性:在分布式系统中,需要确保不同服务之间的事务操作是独立的,互相之间没有影响。Spring Cloud Alibaba Seata提供了分布式事务的隔离性功能,可以确保不同服务之间的事务操作是独立的。 3. 并发控制:在分布式系统中,由于多个服务之间存在依赖关系,因此可能会出现并发冲突的情况。Spring Cloud Alibaba Seata提供了并发控制功能,可以确保多个服务之间的并发操作不会冲突。 4. 事务回滚:在分布式系统中,如果某个服务的事务操作失败,需要对整个事务进行回滚。Spring Cloud Alibaba Seata提供了事务回滚功能,可以确保在分布式环境下的事务回滚操作是可靠的。 5. 可靠性:在分布式系统中,需要确保事务操作是可靠的,不会出现数据丢失或者数据不一致的情况。Spring Cloud Alibaba Seata提供了高可靠性的事务管理功能,可以确保分布式事务操作的可靠性和安全性。 综上所述,Spring Cloud Alibaba Seata是一款非常强大的分布式事务解决方案,可以帮助开发人员在分布式环境下处理复杂的事务操作,确保数据的一致性和完整性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值