上篇文章学习了如何安装seata,先学习如何使用
事务的4个特性ACID
事务特性
at模式详解
AT模式运行机制
AT模式的特点就是对业务无入侵式,整体机制分二阶段提交
两阶段提交协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
-
- 提交异步化,非常快速地完成。
-
- 回滚通过一阶段的回滚日志进行反向补偿。
在 AT 模式下,用户只需关注自己的业务SQL,用户的业务SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
- 回滚通过一阶段的回滚日志进行反向补偿。
Seata具体实现步骤
- TM端使用@GlobalTransaction进行全局事务开启、提交、回滚
- TM开始RPC调用远程服务
- RM端seata-client通过扩展DataSourceProxy,实现自动生成UNDO_LOG与TC上报
- TM告知TC提交/回滚全局事务
- TC通知RM各自执行commit/rollback操作,同时清除undo_log
RM实现
- 创建订单和库存服务的DB
-- 库存服务DB执行
CREATE TABLE `tab_storage` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`total` int(11) DEFAULT NULL COMMENT '总库存',
`used` int(11) DEFAULT NULL COMMENT '已用库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `tab_storage` (`product_id`, `total`,`used`)VALUES ('1', '96', '4');
INSERT INTO `tab_storage` (`product_id`, `total`,`used`)VALUES ('2', '100','0');
-- 订单服务DB执行
CREATE TABLE `tab_order` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`count` int(11) DEFAULT NULL COMMENT '数量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金额',
`status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完成',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 各数据库加入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;
- 编写业务代码
库存代码
package com.seata.storage;
import com.seata.storage.model.Storage;
import com.seata.storage.service.StorageService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
@MapperScan("com.seata.storage.mapper")
@EnableDiscoveryClient
@EnableFeignClients
public class StorageApplication {
@Autowired
private StorageService storageService;
@GetMapping("storage/change")
public Boolean change(long used , long productId){
Storage storage = new Storage();
storage.setTotal(100)
.setProductId(productId)
.setUsed(3);
return storageService.create(storage);
}
public static void main(String[] args) {
SpringApplication.run(StorageApplication.class, args);
}
}
订单代码
package com.seata.order;
import com.seata.order.model.Order;
import com.seata.order.service.OrderService;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
@SpringBootApplication
@MapperScan("com.seata.order.mapper")
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
@Autowired
private OrderService orderService;
@GetMapping("order/create")
public Boolean create(long userId , long productId){
Order order = new Order();
order.setCount(1)
.setMoney(BigDecimal.valueOf(88))
.setProductId(productId)
.setUserId(userId)
.setStatus(0);
return orderService.create(order);
}
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- 测试业务代码是否正常运行(完整代码见附件链接:https://pan.baidu.com/s/1vnaulywP-GLk5rGvtJulPg?pwd=e7vk
提取码:e7vk) - TM实现
搭建business服务,pom、bootstrap.yml与RM基本一致,并提供FeignClient调用组件
FeignClient组件代码编写
@FeignClient(value = "storage-service")
@Component
public interface StorageClient {
@GetMapping("api/storage/change")
Boolean changeStorage(@RequestParam("productId") long productId , @RequestParam("used") int used);
}
@FeignClient(value = "order-service")
@Component
public interface OrderClient {
@GetMapping("api/order/create")
Boolean create(@RequestParam("userId") long userId , @RequestParam("productId") long productId);
}
调用层代码编写
package com.seata.business;
import com.seata.business.feign.OrderClient;
import com.seata.business.feign.StorageClient;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
@EnableFeignClients
@EnableDiscoveryClient
public class BusinessApplication {
@Autowired
private OrderClient orderClient;
@Autowired
private StorageClient storageClient;
@GetMapping("buy")
@GlobalTransactional
public String buy(long userId , long productId){
orderClient.create(userId , productId);
storageClient.changeStorage(userId , 1);
return "ok";
}
public static void main(String[] args) {
SpringApplication.run(BusinessApplication.class, args);
}
}
TM测试请求
如果在order与storage服务器出现以下语句表示分布式事务成功