介紹
Seata是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務。
分布式事務處理過程一ID+三組件模型:
Transaction ID XID 全局唯一的事務ID
三組件:
TC (Transaction Coordinator) - 事務協調者:維護全局和分支事務的狀態,驅動全局事務提交或回滾。
TM (Transaction Manager) - 事務管理器:定義全局事務的范圍:開始全局事務、提交或回滾全局事務。
RM (Resource Manager) - 資源管理器:管理分支事務處理的資源,與TC交談以注冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾。
Seata處理過程:
1.TM向TC申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的XID.
2.XID在微服務調用鏈路的上下文中傳播。
3.RM向TC注冊分支事務,將其納入XID對應全局事務的管轄。
4.TM向TC發起針對XID的全局提交或回滾決議。
5.TC調度XID下管轄的全部分支事務完成提交或回滾請求。
安裝
從github上下載seata-server09的zip包,解壓之后,進入conf目錄,修改配置文件file.conf
然后在本機創建seata數據庫,並執行conf目錄下的db_store.sql文件
修改register.conf配置文件,修改注冊中心為nacos,並配置連接地址,雙擊bin目錄下的seata-server.bat.
建庫
CREATE DATABASE seata_order;
USE seata_order;
CREATE TABLE t_order(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
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已完結'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO `seata_order`.`t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`) VALUES ('51', '1', '1', '10', '100', '1');
INSERT INTO `seata_order`.`t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`) VALUES ('52', '1', '1', '10', '100', '1');
INSERT INTO `seata_order`.`t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`) VALUES ('54', '1', '1', '10', '100', '1');
INSERT INTO `seata_order`.`t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`) VALUES ('55', '1', '1', '10', '100', '0');
CREATE DATABASE seata_storage;
USE seata_storage;
CREATE TABLE t_storage(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
product_id BIGINT(11) DEFAULT NULL COMMENT '產品id',
total INT(11) DEFAULT NULL COMMENT '總庫存',
used INT(11) DEFAULT NULL COMMENT '已用庫存',
residue INT(11) DEFAULT NULL COMMENT '剩余庫存'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);
CREATE DATABASE seata_account;
USE seata_account;
CREATE TABLE t_account(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
user_id BIGINT(11) DEFAULT NULL COMMENT '用戶id',
total DECIMAL(10,0) DEFAULT NULL COMMENT '總額度',
used DECIMAL(10,0) DEFAULT NULL COMMENT '已用額度',
residue DECIMAL(10,0) DEFAULT 0 COMMENT '剩余可用額度'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);
建完三個數據庫后,分別在三個庫中執行conf目錄下的db_undo_log.sql回滾表
總計:
搭建微服務
業務需求:下訂單->減庫存->扣余額->改訂單狀態
三個微服務只示范訂單微服務:
pom依賴:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
配置文件:
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
#事務組的名稱,與file.conf中的組名相同
tx-service-group: wj_group
nacos:
discovery:
server-addr: 192.168.10.137:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order
username: root
password: 1234
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
主啟動類:
@MapperScan("com.wj.springcloud.dao")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderMainApp2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMainApp2001.class,args);
}
}
配置類:
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSourceProxy);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources(mapperLocations));
return bean.getObject();
}
}
service,controller和mapper以及mapper.xml省略
使用@GlobalTransactional注解開啟分布式事務
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private AccountService accountService;
@Autowired
private StorageService storageService;
@Override
@GlobalTransactional
public void create(Order order) {
log.info("---->開始新建訂單");
orderDao.create(order);
log.info("---->訂單微服務開始調用庫存,做扣減");
storageService.decrease(order.getProductId(),order.getCount());
log.info("---->訂單微服務開始調用賬戶,做扣減");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("---->修改訂單狀態");
orderDao.update(order.getUserId(),0);
log.info("下訂單結束");
}
}
@Component
@FeignClient(value="seata-account-service")
public interface AccountService {
@PostMapping("/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("count") BigDecimal count);
}
@Component
@FeignClient(value="seata-storage-service")
public interface StorageService {
@PostMapping("/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);
}
以往所有SpringCloud代碼已上傳至github:https://github.com/JGZY/cloud2020