Seata是一个开源的分布式事务解决方案,致力于提供高性能、易用的分布式事务服务。Seata将为用户提供AT、TCC、SAGA、XA交易模型,为用户打造一站式分布式解决方案。
两个阶段提交协议的演变:
阶段1: 在同一个本地事务中提交业务数据(也就是真正的修改数据库)和回滚日志,然后释放本地锁和连接资源。
阶段2: 对于提交案例,异步快速地完成工作。 对于回滚情况,根据第一阶段创建的回滚日志进行补偿
@GlobalTransactional
的就可以看作是个TM事务管理者,当然它同时也是个RM; 比如,订单服务下单成功后调用库存服务减库存,订单服务的方法上会标注@GlobalTransactional
,作为一个TM(库存服务这不需要标注),来管理订单和库存两个服务的整体事务;seata-serve
r服务,用来保存全局的事务,分支事务,全局锁,然后通知各个RM进行事务提交或回滚;准备:
第一步:下载1.1.0版本的TC服务,并解压,windows系统在解压后的文件找到.bat文件即可启动
紧接者会弹出dos窗口。输出stared就说明TC启动成功了;
注意
:每个业务库里面都需要有张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;
<!--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那种配置代理对象了。
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文件的坑点:
vgroup_mapping
vgroup_mapping.取spring.application.name的值-fescar-service-group = "default"
那么我这段配置就是vgroup_mapping.seata-order-6500-service-fescar-service-group = “default”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;
}
}