上篇文章学习了如何安装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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate OrderClient orderClient;@Autowiredprivate StorageClient storageClient;@GetMapping("buy")@GlobalTransactionalpublic 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服务器出现以下语句表示分布式事务成功