分布式事务——集成seata

摘要:

       Seata是由阿里中间件团队发起的开源项目Fescar,后更名为Seata,是一个开源的分布式事务框架,共有三个角色:RM资源管理器、TM事务管理器、TC事务协调器,在开启全局事务的时候,资源管理器会向事务管理器发起请求,告诉它我们要开启全局事务,需要拿到XID,事务管理器就会向事务协调器获取一个本次全局事务的唯一XID交给当前资源管理器,资源管理器拿到后将本地事务根据XID加入到全局事务,然后本地记录Unlog日志,再提交本地事务,最后上报事务状态【成功or失败】,事务协调器根据上报的状态处理全局事务,成功则删除日志,失败则根据事务进行反向操作回滚,它是强一致性的

分布式事务集成Seata

一:下载Seata中间件的客户端

  https://github.com/seata/seata/tags

  启动指令:

  seata-server.bat -p 8091 -h 127.0.0.1 -m file

二:引入依赖

 由于Seata需要操作数据库【根据Unlog日志回滚操作】,所以需要导入数据库的配置文件以及连接池以及本身的seata依赖【如果是MyBatis则引入MyBatis依赖,此处为MyBatisPlus】

<!--mybatisplus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-support</artifactId>
    <version>2.2.0</version>
    <scope>compile</scope>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid连接池-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
</dependency>
<!--seata依赖,TM+RM集成包-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

三:引入配置文件file.conf和register.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 {
	  #transaction service group mapping
	  vgroupMapping.fsp_tx_group = "default"
	  #only support when registry.type=file, please don't set multiple addresses
	  default.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
	  }
	}

  register.conf:

	registry {
	  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
	  type = "file"
	
	  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"
	  }
	}

四:配置yml

seata:
  enableAutoDataSourceProxy: false #关闭DataSource代理的自动配置,我们要手动配置
spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: fsp_tx_group #这里和file.conf中事务组名一样
  datasource:
    username: root
    password: root
    url: jdbc:mysql:///ymcc-uaa?serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource  #指定数据库类型
mybatis-plus:
  mapper-locations: classpath:cn/ybl/mapper/*Mapper.xml

五:配置数据源【MyBatisPlus版本】

package cn.itsource.ymcc.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
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 DataSourceConfiguration {

    //mapper.xml路径
    @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocations;

    //手动配置bean
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        //处理MybatisPlus
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSourceProxy);
        factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        //事务管理工厂
        factory.setTransactionFactory(new SpringManagedTransactionFactory());
        return factory;
    }

    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}

MyBatis版本

package cn.itsource.hrm.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.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

//使用seata对DataSource进行代理
@Configuration
public class DataSourceProxyConfig {

    //mapper.xml路径
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    //手动配置bean
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public SqlSessionFactory sessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSourceProxy);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        //事务管理工厂
        sessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sessionFactoryBean.getObject();
    }

    @Bean
    public DataSourceProxy dataSource() {
        return new DataSourceProxy(druidDataSource());
    }
}

六:启动类排除DataSource自动配置

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class})

七:在需要添加全局事务的方法上面打上注解【一般来说,调用了feign接口的方法都要保证数据的一致性】

@GlobalTransactional(rollbackFor = Exception.class) //开启Seata全局事务

八:将MyBatisPlusConfig注册分页插件配置类的事务注解去除

九:数据库创建nolog日志表,用于seata记录事务回滚操作

查看代码
-- 注意此处0.3.0+ 增加唯一索引 ux_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=1 DEFAULT CHARSET=utf8;

十:方法的被调方集成seata与调用方集成seata的方法一致,但是不用在方法上加开启全局注解的操作

posted @   yyybl  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示