SpringCloud+Seata分布式事务(AT模式)

什么是 Seata?

Seata是一个开源的分布式事务解决方案,致力于提供高性能、易用的分布式事务服务。Seata将为用户提供AT、TCC、SAGA、XA交易模型,为用户打造一站式分布式解决方案。

整体机制:

两个阶段提交协议的演变:
阶段1: 在同一个本地事务中提交业务数据(也就是真正的修改数据库)和回滚日志,然后释放本地锁和连接资源。
阶段2: 对于提交案例,异步快速地完成工作。 对于回滚情况,根据第一阶段创建的回滚日志进行补偿

概念认识:

  1. RM(ResourceManager) ,可以理解为一个一个的微服务,也叫事务参与者;
  2. TM(TranactionManager) 也是一个微服务,但是该微服务是事务的带头大哥,凡是标注@GlobalTransactional的就可以看作是个TM事务管理者,当然它同时也是个RM; 比如,订单服务下单成功后调用库存服务减库存,订单服务的方法上会标注@GlobalTransactional,作为一个TM(库存服务这不需要标注),来管理订单和库存两个服务的整体事务;
  3. TC(全局事务协调者) 也就是seata-server服务,用来保存全局的事务,分支事务,全局锁,然后通知各个RM进行事务提交或回滚;

SpringCloud + Seata(AT模式+文件模式) 的集成

准备:

  • springcloud微服务:
    • seata-order-6500-service订单服务;
    • seata-stock-6502-service库存服务;
  • TC:
    • seata-server服务版本: seata-server-1.1.0.zip

第一步:下载1.1.0版本的TC服务,并解压,windows系统在解压后的文件找到.bat文件即可启动
SpringCloud+Seata分布式事务(AT模式)_第1张图片
紧接者会弹出dos窗口。输出stared就说明TC启动成功了;
SpringCloud+Seata分布式事务(AT模式)_第2张图片

第二步:创建订单库库存服务的数据库

SpringCloud+Seata分布式事务(AT模式)_第3张图片
注意:每个业务库里面都需要有张undo_log
1.收到 TC 的回滚请求后,开始本地事务,执行如下操作。
2.通过 XID 和分支 ID 检索 UNDO LOG。
3.验证数据:将UNDO LOG中更新后的图像数据与当前数据进行比较,如果有差异,说明数据被当前事务外的操作改变了,应该在不同的策略中处理。
4.根据UNDO LOG中的before image和业务SQL的相关信息生成回滚SQL语句

	#undo_log表结构,每个业务数据库下都需要有此表
	
	CREATE TABLE `undo_log` (
	  `id` bigint NOT NULL AUTO_INCREMENT,
	  `branch_id` bigint NOT NULL,
	  `xid` varchar(100) NOT NULL,
	  `context` varchar(128) NOT NULL,
	  `rollback_info` longblob NOT NULL,
	  `log_status` int 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=9 DEFAULT CHARSET=utf8;

第三步:在6500订单服务上引入seata的依赖

	<!--seata分布式事务-->
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-alibaba-dependencies</artifactId>
		<version>2.2.0.RELEASE</version>
		<type>pom</type>
		<scope>import</scope>
	</dependency>
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
		<version>2.2.0.RELEASE</version>
	</dependency>

注意:这里我用的是2.2.0的依赖,seata在2.2.0版本进行了数据源自动代理,不需要像2.1.0那种配置代理对象了。

第四步:在解压后seata-server的conf目录下将下图的两个文件拷贝到微服务的resource,目录下。然后将file.conf.example重命名为file.conf

SpringCloud+Seata分布式事务(AT模式)_第4张图片
微服务的resource目录结构:
SpringCloud+Seata分布式事务(AT模式)_第5张图片

第五步:6500微服务配置修改

  • registry.conf文件我是用的就是file所以这个文件不做修改;
    略。。。

  • 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 = false
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThreadPrefix = "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 configuration, only used in client side
service {
  #transaction service group mapping
  vgroup_mapping.取spring.application.name的值-fescar-service-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 transaction configuration, only used in client side
client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
    sqlParserType = druid
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

## transaction log store, only used in server side
store {
  ## store mode: file、db
  mode = "file"
  ## 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) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "mysql"
    password = "mysql"
    minConn = 1
    maxConn = 10
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
  }
}
# server configuration, only used in server side
server {
  recovery {
    #schedule committing retry period in milliseconds
    committingRetryPeriod = 1000
    #schedule asyn committing retry period in milliseconds
    asynCommittingRetryPeriod = 1000
    #schedule rollbacking retry period in milliseconds
    rollbackingRetryPeriod = 1000
    #schedule timeout retry period in milliseconds
    timeoutRetryPeriod = 1000
  }
  undo {
    logSaveDays = 7
    #schedule delete expired undo_log in milliseconds
    logDeletePeriod = 86400000
  }
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  maxCommitRetryTimeout = "-1"
  maxRollbackRetryTimeout = "-1"
  rollbackRetryTimeoutUnlockEnable = false
}

# metrics configuration, only used in server side
metrics {
  enabled = false
  registryType = "compact"
  # multi exporters use comma divided
  exporterList = "prometheus"
  exporterPrometheusPort = 9898
}

注意file.conf文件的坑点:

  • 在file.conf的文件中我依旧使用的file类型的配置,所以我需要修改的地方就是server{ . . . . . . . }
    • 这里从解压文件拷贝过来的时候vgroup_mapping是驼峰命名vgroupMapping需要改成下滑线的命令格式vgroup_mapping
    • vgroup_mapping的组成在这个版本是有规则的:vgroup_mapping.取spring.application.name的值-fescar-service-group = "default" 那么我这段配置就是vgroup_mapping.seata-order-6500-service-fescar-service-group = “default”

第六步:添加数据源的配置:

yml数据源配置:
SpringCloud+Seata分布式事务(AT模式)_第6张图片

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;

@Configuration
public class DataSourceProxyConf {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

第七步:编写订单服务和库存服务的业务代码

6500订单服务,功能下单>>扣库存

@Service
public class OrderServiceImp implements IOrderService {
	
	@Autowired(required=true)
	private OrderDao dao;

	@Autowired(required=true)
	private IStockClientService stockClientService;

	@GlobalTransactional(rollbackFor = Exception.class)
	@Override
	public String createOrder() {
		OrderDO orderDO = new OrderDO();
		orderDO.setOrderAmount(new BigDecimal("100"));
		orderDO.setOrderName("华为P40");
		orderDO.setOrderNo(System.currentTimeMillis()+"");
		//6500订单服务创建订单
		Integer insert = dao.insert(orderDO);
		//6502库存服务扣减库存
		Boolean aBoolean = stockClientService.subStock();
		return aBoolean?"ok":"fail";
	}
}

6502库存服务扣减库存

@Slf4j
@Service
public class StockServiceImp implements IStockService {
	@Autowired(required=true)
	private StockDao dao;
	//被调用方可以不写@GlobalTransactional
	//@GlobalTransactional
	@Override
	public Boolean subStock() {
		log.info("开始扣减库存");
		Integer i = dao.subStock();
		log.info("库存扣减结束 i:{}",i);
		//抛出异常
		int f = 1/0;
		return i>0;
	}
}

第八步:6502服务也按照6500的配置一样即可,只是file.conf文件的vgroup_mapping改一下即可,我的6502的配置是 vgroup_mapping.seata-order-6500-service-fescar-service-group = “default”

第九步:启动

  • 先启动seata-service , 再分别启动6500和6502两个微服务

在这里插入图片描述

  • 到这里证据springcloud集成seata就结束了;

第十步:测试

  • 订单服务掉库存服务,库存服务发生异常,那么订单服务回滚不会记录订单,库存服务回滚不会扣减库存;

你可能感兴趣的:(分布式,分布式,java,mysql,spring,数据库)