Seata is an easy-to-use, high-performance, open source distributed transaction solution.
Seata 是一个简单易用的,高性能,开源的分布式事务解决方案。
AT
模式是一种无侵入的分布式事务解决方案。在 AT
模式下,用户只需关注自己的“业务 SQL
”,
用户的 “业务 SQL
” 就是全局事务一阶段,Seata
框架会自动生成事务的二阶段提交和回滚操作。
AT
模式如何做到对业务的无侵入 :
一阶段:
在一阶段,Seata
会拦截“业务 SQL
”,首先解析 SQL
语义,找到“业务 SQL
”要更新的业务数据,
在业务数据被更新前,将其保存成before image
,然后执行“业务 SQL
”更新业务数据,在业务数据更新之后,
再将其保存成after image
,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交:
二阶段如果是提交的话,因为“业务 SQL
”在一阶段已经提交至数据库, 所以 Seata
框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚:
二阶段如果是回滚的话,Seata
就需要回滚一阶段已经执行的“业务 SQL
”,还原业务数据。
回滚方式便是用before image
还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 after image
,
如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
AT
模式的一阶段、二阶段提交和回滚均由 Seata
框架自动生成,用户只需编写“业务 SQL
”,便能轻松接入分布式事务,AT
模式是一种对业务无任何侵入的分布式事务解决方案。
(以上选自知乎分布式事务的4种模式)
nacos-dubbo
nacos-dubbo
是一个简单的Seata AT
模式的入门项目,使用dubbo
去实现服务与服务之间的调用。
provider
项目中只有2种类型的项目一种是api
,还有一种是service
所谓api
项目是项目只有接口和domain
,没有实现。此项目会被该api
的实现(service
)和需要调用service
的项目依赖。
service
项目是实现api
项目的项目一般是去操作数据库或者其他业务的项目。
consumer
项目也只有2种类型的项目一种是api
,还有一种是service
和上面一样,service
项目会去调用对应的provider
的接口,使用的是dubbo rpc
的通讯
business
项目是指业务层的代码项目,他会去依赖consumer
的api
项目。然后对外部提供RESTFul
的接口
所以在这个项目中,先启动2个provider
中的service
,然后启动consumer
的service
,最后启动business
项目。
首先需要创建2个库,一个库存放order
表,一个库存放order_item
。
CREATE TABLE tb_order (
id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT(20) NOT NULL,
user_id BIGINT(20) NOT NULL);
CREATE TABLE tb_order_item (
id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT(20) NOT NULL,
order_id BIGINT(20) NOT NULL,
order_item_id BIGINT(20) NOT NULL);
除此之外,还需要在每个库里都创建一个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
依赖
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>1.0.0version>
dependency>
配置文件
spring
alibaba:
seata:
# 自定义事务组名称 tx_group,需要与服务端一致
tx-service-group: tx_group
配置类,具体看项目的SeataConfiguration
import io.seata.rm.datasource.DataSourceProxy;
@Configuration
public class SeataConfiguration {
@Primary
@Bean("dataSource")
public DataSourceProxy dataSource(DataSource hikariDataSource) {
return new DataSourceProxy(hikariDataSource);
}
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
/**
* applicationId:同服务名即可
* txServiceGroup:自定义事务组名,需要与 Seata Server 配置一致
*/
return new GlobalTransactionScanner("provider-order-item", "tx_group");
}
}
一定要在启动类上加上@EnableTransactionManagement
这里删除去了 mybatis mysql hikari
的依赖。
配置类
import io.seata.spring.annotation.GlobalTransactionScanner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SeataConfiguration {
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
return new GlobalTransactionScanner("consumer-transaction", "tx_group");
}
}
实现方法上加上注解@GlobalTransactional
@Service(version = "1.0.0")
public class TransactionServiceImpl implements TransactionService {
@Reference(version = "1.0.0")
private OrderService orderService;
@Reference(version = "1.0.0")
private OrderItemService orderItemService;
@Override
@GlobalTransactional
public void doTransaction(TbOrder tbOrder, TbOrderItem tbOrderItem) throws Exception {
System.out.println("transaction 开始全局事务,XID = " + RootContext.getXID());
orderService.insert(tbOrder);
orderItemService.insert(tbOrderItem);
if (tbOrderItem.getOrderId() == null ) {
throw new RuntimeException(" null Exception ");
}
}
}
这里就正常调用consumer
@RestController
@RequestMapping("/v1/transaction")
public class TransactionController {
@Reference(version = "1.0.0")
private TransactionService transactionService;
@GetMapping("")
public Map<String,Object> doTransaction() throws Exception {
TbOrder order = new TbOrder();
order.setOrderId(1L);
order.setUserId(1L);
TbOrderItem orderItem = new TbOrderItem();
orderItem.setUserId(1L);
transactionService.doTransaction(order,orderItem);
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("message","服务调用成功");
return map;
}
}
接下来就是浏览器访问测试 http://127.0.0.1:27010/v1/transaction
再到Seata
服务上看到回滚的日志,再查看数据库。成功回滚
nacos-http
(TBD)nacos-http
是一个使用Spring Cloud Alibaba
实现Seata AT
模式的入门项目。