文章知识来源主要来源于:赵俊夫先生的博客 以下为原文链接
https://blog.csdn.net/u011177064/category_9572944.html
seata官方文档:
http://seata.io/zh-cn/docs/overview/what-is-seata.html
1:为什么选择AT模式
经过介绍对AT模式的优缺点都很明显。
优点:
1:完全没有代码嵌入 使用简单粗暴
2:通过加锁的方式,可以保证分布式事务的强一致性。
缺点:由于数据库加锁太多 导致高并发场景下效率偏低
对于:普通公司的业务区场景已经是完全够用了
2:Seata 的AT模式 下载资源
使用官方Demo
https://github.com/seata/seata-samples/tree/master/springboot-mybatis
如果下载单个Demo模块的文件:
1:进入网站 http://kinolien.github.io/gitzip/?
2:第一次使用需要Github秘钥
如何获取Github API Accsess token
Github->Settings->Developer settings -> Personal accss tokens
ghp_pEumyQ5cGgpWkfHXHuDVmUR6JoWQGt0qiXXX
3:输入 https://github.com/seata/seata-samples
点击 Search 下载
下载seata服务端
从 https://github.com/seata/seata/releases,下载服务器软件包,将其解压缩。
点击 seata/bin/ 下的 seata-server.bat 进行启动
启动端口 8091 (window上找不到修改的方法)
Usage: sh seata-server.sh(for linux and mac) or cmd seata-server.bat(for windows) [options]
Options:
--host, -h
The host to bind.
Default: 0.0.0.0
--port, -p
The port to listen.
Default: 8091
--storeMode, -m
log store mode : file、db
Default: file
--help
e.g.
3:初始化分布式数据库SQL
# Storage
DROP SCHEMA IF EXISTS db_storage;
CREATE SCHEMA db_storage;
USE db_storage;
CREATE TABLE `storage_tbl`
(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
INSERT INTO storage_tbl (id, commodity_code, count)
VALUES (1, '2001', 1000);
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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
# Order
DROP SCHEMA IF EXISTS db_order;
CREATE SCHEMA db_order;
USE db_order;
CREATE TABLE `order_tbl`
(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT '0',
`money` INT(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
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;
4:代码编写 Pom新增Maven包
除 Feign服务 其他所有服务新增以下包
1:需要使用mysql作为持久化数据库 mybatis作为持久化ORM框架
2:引入seata 相关包
<!-- Mysql 驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!-- 连接Mybatis持久层框架-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- Mybatis 驱动包 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- seata分布式事务-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.4.0</version>
</dependency>
5:代码编写 yml新增配置
Feign服务 其他所有服务 yml新增以下配置
实际上:config 与 registry 也能在\seata\conf 下的 registry.conf进行配置
registry.conf 实际上可以和nacos 等服务注册发现中心配合使用
这里为了方便 把config 与 registry 设置为 file类型
相应的存储文件存放在 \seata\bin\sessionStore 的下 root.data
seata:
enabled: true
application-id: nacos-consumer
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: file
registry:
type: file
6:修改service-consumer-demo 服务
Seata 的事务上下文由 RootContext 来管理。
应用开启一个全局事务后,RootContext 会自动绑定该事务的 XID,
事务结束(提交或回滚完成),RootContext 会自动解绑 XID。
该XID为1个分布式事务标识。
seata文档
http://seata.io/zh-cn/docs/user/microservice.html
XID 192.168.137.1:8091:126826542571458560
1:外部请求进来 由于使用了@GlobalTransactional注解 获取XID
2:拦截器 在进行Feign调用的时候 如果XID存在 则打印一下
3:拦截器 在进行Feign调用的时候 把XID(不管是否存在)存放到到请求头
注意:
RootContext.getXID() 只是为了打印一下 而不是生成
@Service
@Slf4j
public class BusinessServiceImpl implements BusinessService {
@Autowired
StorageFeignClientService storageFeignClientService;
@Autowired
ProviderFeignClientService providerFeignClientService;
/**
* 通过 storageFeignClientService 调用 storage服务 进行库存的减少
* 通过 providerFeignClientService 调用 provider服务 进行订单的新增
*
* @param userId
* @param commodityCode
* @param orderCount
*/
@Override
@GlobalTransactional(rollbackFor = RuntimeException.class)
public void purchase(String userId, String commodityCode, Integer orderCount) {
//为了与日志区分 使用 System打印日志
System.out.println("进入purchase 购买下单,模拟全局事务提交 请求");
System.out.println("order XID " + RootContext.getXID());
//storageService服务 db_storage库 storage_tbl表
storageFeignClientService.seatadeductStorage(commodityCode, orderCount);
//providerService服务 db_order库 order_tbl表
providerFeignClientService.seataCreateOrder(userId, commodityCode, orderCount);
}
}
/**
* seata 的 AT 模式的 标志 存放到请求头里面
* HeSuiJin
*/
@Configuration
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String xid = RootContext.getXID();
if (StringUtils.isNotBlank(xid)) {
System.out.println("feign xid:"+xid);
}
requestTemplate.header(RootContext.KEY_XID, xid);
}
}
7:新增服务 service-storage-demo
该服务和 service-provider-demo 的结构一致
新增接口 /seataTest/deductStorage
被storage-feign调用 进行库存的减少逻辑
storageService服务 db_storage库 storage_tbl表
8:修改service-provider-demo 服务
新增接口 /seataTest/createOrder
被provider-feign调用 进行订单的新增逻辑
providerService服务 db_order库 order_tbl表
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 创建订单
* @param userId
* @param commodityCode
* @param orderCount
*/
@Override
public void createOrder(String userId, String commodityCode, Integer orderCount) {
BigDecimal orderMoney = new BigDecimal(orderCount).multiply(new BigDecimal(5));
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(orderCount);
order.setMoney(orderMoney);
orderMapper.insert(order);
//先进 storage服务 执行完逻辑
//后进 provider服务 使得产生RuntimeException异常
//storage服务 和 provider服务 都回滚
if (true) {
throw new RuntimeException("provider service createOrder exception");
}
}
}
项目连接
请配合项目代码食用效果更佳:
项目地址:
https://github.com/hesuijin/spring-cloud-alibaba-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/spring-cloud-alibaba-project.git
https://github.com/hesuijin/spring-cloud-alibaba-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/spring-cloud-alibaba-project.git
在service-storage-demo 模块下
在service-consumer-demo 模块下
在service-provider-demo 模块下
在service-provider-demo-other 模块下
网友评论