Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案
Seata 官网:https://seata.io/zh-cn/
Spring Cloud Alibaba 官网:https://sca.aliyun.com/zh-cn/
版本说明
SpringBoot 版本 2.6.5
SpringCloud 版本 2021.0.1
SpringCloudAlibaba 版本 2021.0.1.0
本文详细说明
数据库服务器版本 mysql 8.0.25
mybatis plus 版本 3.5.1
nacos 版本 1.4.2
seata 客户端版本 1.4.2
seata 服务端版本 1.7.1
本文讲解的是 seata 的 XA 事物模型,在开始阅读下面内容之前,建议先阅读笔者的这篇文章《Spring Cloud Alibaba Seata 实现分布式事物》,这篇文章中实现的是 seata 的 AT 事物,且笔者的本篇文章《Spring Cloud Alibaba Seata 实现 XA 事物》是在《Spring Cloud Alibaba Seata 实现分布式事物》基础上写的,很多内容需要先了解,涉及seata 和nacos的重复内容,笔者在本篇文章中不在赘述,因此建议读者先看《Spring Cloud Alibaba Seata 实现分布式事物》,之后再学习本篇文章。当然,如果你对 seata 的搭建已经非常熟悉,那么可以直接开始下面阅读
XA 是由Tuxedo首先提出的,并交给X/Open组织。XA 是一个分布式事务协议。XA 协议主要定义了事务管理器TM(Transaction Manager,协调者)和资源管理器RM(Resource Manager,参与者)之间的接口。其中,资源管理器往往由数据库实现,如Oracle、DB2、MySQL,这些商业数据库都实现了XA 接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA 事务是基于两阶段提交(Two-phaseCommit,2PC)协议实现的,可以保证数据的强一致性。阶段一为准备阶段,所有的参与者准备执行事务并锁住需要的资源,当参与者Ready时,向TM 汇报自己已经准备好了。阶段二为提交阶段,当TM 确认所有参与者都Ready 后,向所有参与者发送COMMIT 命令提交
seata XA 事物官网文档:https://seata.io/zh-cn/docs/dev/mode/xa-mode
目录
1、创建项目
1.1、新建 maven 聚合项目 cloud-learn
1.2、创建 account 服务
1.3、创建 order 服务
2、添加配置
2.1、客户端配置
2.2、服务端配置
3、数据库建表
3.1、seata 服务端建表
3.2、seata 客户端建表
4、运行测试
5、项目代码
最外层父工程 cloud-learn 的 pom.xml
4.0.0
com.wsjzzcbq
cloud-learn
1.0-SNAPSHOT
gateway-learn
consumer-learn
sentinel-learn
seata-at-account-learn
seata-at-order-learn
seata-tcc-order-learn
seata-tcc-account-learn
seata-xa-account-learn
seata-xa-order-learn
pom
naxus-aliyun
naxus-aliyun
https://maven.aliyun.com/repository/public
true
false
org.springframework.boot
spring-boot-starter-parent
2.6.5
2021.0.1
2021.0.1.0
2021.1
2021.1
3.1.1
1.1.17
8.0.11
3.5.1
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
${spring-cloud-alibaba.version}
pom
import
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
${alibaba-nacos-discovery.veriosn}
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
${alibaba-nacos-config.version}
org.springframework.cloud
spring-cloud-starter-bootstrap
${spring-cloud-starter-bootstrap.version}
com.alibaba.fastjson2
fastjson2
2.0.40
org.projectlombok
lombok
下面会创建2个服务 account 和 order,模拟用户下订单后扣减账户金额,服务间使用 feign 调用,因为 account 和 order 服务使用不同的数据库,因此产生分布式事物,使用 seata 解决
创建子工程 seata-xa-account-learn
seata-xa-account-learn pom 文件
cloud-learn
com.wsjzzcbq
1.0-SNAPSHOT
4.0.0
seata-xa-account-learn
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
com.alibaba
druid-spring-boot-starter
${druid.version}
mysql
mysql-connector-java
${mysql.version}
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
org.springframework.boot
spring-boot-maven-plugin
启动类 SeataXAAccountApplication
package com.wsjzzcbq;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SeataXAAccountApplication
*
* @author wsjz
* @date 2023/10/24
*/
@MapperScan(value = {"com.wsjzzcbq.mapper"})
@SpringBootApplication
public class SeataXAAccountApplication {
public static void main(String[] args) {
SpringApplication.run(SeataXAAccountApplication.class, args);
}
}
实体类 Account
package com.wsjzzcbq.bean;
import lombok.Data;
/**
* Account
*
* @author wsjz
* @date 2022/07/07
*/
@Data
public class Account {
private Integer id;
private String userId;
private Integer money;
}
AccountMapper
package com.wsjzzcbq.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wsjzzcbq.bean.Account;
/**
* AccountMapper
*
* @author wsjz
* @date 2023/10/13
*/
public interface AccountMapper extends BaseMapper {
}
AccountService
package com.wsjzzcbq.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wsjzzcbq.bean.Account;
/**
* AccountService
*
* @author wsjz
* @date 2023/10/13
*/
public interface AccountService extends IService {
String reduce(String userId, int money);
}
AccountServiceImpl
package com.wsjzzcbq.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.mapper.AccountMapper;
import com.wsjzzcbq.service.AccountService;
import io.seata.core.context.RootContext;
import org.springframework.stereotype.Service;
/**
* AccountServiceImpl
*
* @author wsjz
* @date 2023/10/13
*/
@Service
public class AccountServiceImpl extends ServiceImpl implements AccountService {
@Override
public String reduce(String userId, int money) {
String xid = RootContext.getXID();
System.out.println(xid);
UpdateWrapper up = new UpdateWrapper<>();
String sql = "money = money - " + money;
up.setSql(sql);
up.eq("user_id", userId);
this.update(up);
return "ok";
}
}
AccountController
package com.wsjzzcbq.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wsjzzcbq.bean.Account;
import com.wsjzzcbq.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* AccountController
*
* @author wsjz
* @date 2023/10/13
*/
@RequestMapping("/account")
@RestController
public class AccountController {
@Autowired
private AccountService accountService;
@GetMapping("/find")
public String find() throws JsonProcessingException {
Account account = accountService.list().get(0);
ObjectMapper objectMapper = new ObjectMapper();
String res = objectMapper.writeValueAsString(account);
System.out.println(res);
return res;
}
@RequestMapping("/reduce")
public String debit(String userId, int money) {
try {
accountService.reduce(userId, money);
return "扣款成功";
} catch (Exception e) {
return "扣款失败";
}
}
}
application.yml 文件
server:
port: 9001
spring:
application:
name: seata-xa-account-learn
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.3.232:3306/pmc-account?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
cloud:
nacos:
username: nacos
password: nacos
server-addr: 192.168.2.140
discovery:
namespace: public
# server-addr: 192.168.2.140
# config:
# server-addr:
seata:
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
group: SEATA_GROUP
data-id: seata-xa.properties
registry:
type: nacos
nacos:
application: seata-server
cluster: default
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
group: SEATA_GROUP
data-source-proxy-mode: XA
# 事物分组,如果不配置默认是spring.application.name + '-seata-service-group'
# tx-service-group:
logging:
level:
com.wsjzzcbq.mapper: debug
配置参数说明可以看《Spring Cloud Alibaba Seata 实现分布式事物》,这里不再赘述
XA 事物配置和 AT 的事物配置的区别是 seata.data-source-proxy-mode=XA,默认是AT
创建子工程 seata-xa-order-learn 项目
seata-xa-order-learn pom 文件
cloud-learn
com.wsjzzcbq
1.0-SNAPSHOT
4.0.0
seata-xa-order-learn
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-loadbalancer
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
com.alibaba
druid-spring-boot-starter
${druid.version}
mysql
mysql-connector-java
${mysql.version}
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
org.springframework.boot
spring-boot-maven-plugin
启动类 SeataXAOrderApplication
package com.wsjzzcbq;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* SeataXAOrderApplication
*
* @author wsjz
* @date 2023/10/24
*/
@MapperScan(value = {"com.wsjzzcbq.mapper"})
@EnableFeignClients
@SpringBootApplication
public class SeataXAOrderApplication {
public static void main(String[] args) {
SpringApplication.run(SeataXAOrderApplication.class, args);
}
}
订单实体类 Order
package com.wsjzzcbq.bean;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* Order
*
* @author wsjz
* @date 2022/07/07
*/
@TableName("order_tbl")
@Data
public class Order {
private Integer id;
private String userId;
private String code;
private Integer count;
private Integer money;
}
OrderMapper
package com.wsjzzcbq.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wsjzzcbq.bean.Order;
/**
* OrderMapper
*
* @author wsjz
* @date 2022/07/07
*/
public interface OrderMapper extends BaseMapper {
}
AccountFeign
package com.wsjzzcbq.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* AccountFeign
*
* @author wsjz
* @date 2023/10/13
*/
@FeignClient(value = "seata-xa-account-learn")
public interface AccountFeign {
@RequestMapping("/account/reduce")
String debit(@RequestParam("userId") String userId, @RequestParam("money") int money);
}
OrderService
package com.wsjzzcbq.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wsjzzcbq.bean.Order;
/**
* OrderService
*
* @author wsjz
* @date 2022/07/07
*/
public interface OrderService extends IService {
void create(String userId, int money, boolean rollback);
}
OrderServiceImpl
package com.wsjzzcbq.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wsjzzcbq.bean.Order;
import com.wsjzzcbq.feign.AccountFeign;
import com.wsjzzcbq.mapper.OrderMapper;
import com.wsjzzcbq.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* OrderServiceImpl
*
* @author wsjz
* @date 2022/07/07
*/
@Service
public class OrderServiceImpl extends ServiceImpl implements OrderService {
@Autowired
private AccountFeign accountFeign;
@GlobalTransactional
@Override
public void create(String userId, int money, boolean rollback) {
String xid = RootContext.getXID();
System.out.println(xid);
String orderCode = UUID.randomUUID().toString();
Order order = new Order();
order.setCode(orderCode);
order.setCount(1);
order.setUserId(userId);
order.setMoney(money);
this.save(order);
accountFeign.debit(userId, money);
if (rollback) {
int a = 1/0;
}
}
}
OrderController
package com.wsjzzcbq.controller;
import com.wsjzzcbq.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* OrderController
*
* @author wsjz
* @date 2022/07/09
*/
@RequestMapping("/order")
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
/**
* http://localhost:9002/order/create?userId=101&money=10&rollback=false
* http://localhost:9002/order/create?userId=101&money=10&rollback=true
* @param userId
* @param money
* @param rollback
* @return
*/
@RequestMapping("/create")
public String create(String userId, int money, boolean rollback) {
try {
orderService.create(userId, money, rollback);
return "下单成功";
} catch (Exception e) {
e.printStackTrace();
return "下单失败";
}
}
}
application.yml 文件
server:
port: 9002
spring:
application:
name: seata-xa-order-learn
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.3.232:3306/pmc-order?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
cloud:
nacos:
username: nacos
password: nacos
server-addr: 192.168.2.140
discovery:
namespace: public
# server-addr: 192.168.2.140
# config:
# server-addr:
seata:
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
group: SEATA_GROUP
data-id: seata-xa.properties
registry:
type: nacos
nacos:
application: seata-server
cluster: default
server-addr: ${spring.cloud.nacos.server-addr}
username: ${spring.cloud.nacos.username}
password: ${spring.cloud.nacos.password}
group: SEATA_GROUP
# 事物分组,如果不配置默认是spring.application.name + '-seata-service-group'
tx-service-group: seata-xa-account-learn-seata-service-group
data-source-proxy-mode: XA
logging:
level:
com.wsjzzcbq.mapper: debug
mybatis-plus:
global-config:
db-config:
id-type: auto
在nacos上新建 group 是 SEATA_GROUP,data-id 是 seata-xa.properties 的配置,内容如下
seata-xa.properties
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.seata-xa-account-learn-seata-service-group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
# You can choose from the following options: fastjson, jackson, gson
tcc.contextJsonParserType=fastjson
#Log rule configuration, for client and server
log.exceptionRate=100
seata-xa.properties 在《Spring Cloud Alibaba Seata 实现分布式事物》的 seata.properties 基础上修改事物分组即可
服务端配置和《Spring Cloud Alibaba Seata 实现分布式事物》保持一致,无需修改
看《Spring Cloud Alibaba Seata 实现分布式事物》seata 服务端建表,保持一致,无需修改
看《Spring Cloud Alibaba Seata 实现分布式事物》seata 客户端建表
undo_log 表不需要,保留 account 和 order_tbl 表即可
启动 seata-server-1.7.1
进入 bin 目录,双击 seata-server.bat
启动 account 和 order 服务
nacos 服务和配置
测试正常情况
浏览器请求:http://localhost:9002/order/create?userId=101&money=10&rollback=false
扣减账户 10 元,新增订单
测试回滚情况
l浏览器请求:http://localhost:9002/order/create?userId=101&money=10&rollback=true
码云地址:https://gitee.com/wsjzzcbq/csdn-blog/tree/master/cloud-learn
至此完