在上篇文章中我们已经介绍了分布式事务的相关概念,以及几种理论模式。这篇文章将从实践出发,基于Seata的AT模式,借助于电商下单的业务场景,实现一个例子。
本文代码示例:gitee.com/zhaowenyi/s…
1. 准备工作
本文将以电商系统中购买商品,扣减商品库存,扣划账户余额为应用场景,基于Seata的AT模式实现一个简单的案例。
1.1 框架准备
- JDK 1.8
- Nacos Server 1.4.2
- Seata Server 1.4.2
1.2 数据库准备
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- seata 框架必备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;
-- 库存表
DROP TABLE IF EXISTS `storage_tbl`;
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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO storage_tbl (commodity_code, count) VALUES ("product_001", 10);
INSERT INTO storage_tbl (commodity_code, count) VALUES ("product_002", 20);
INSERT INTO storage_tbl (commodity_code, count) VALUES ("product_003", 30);
-- 订单表
DROP TABLE IF EXISTS `order_tbl`;
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;
-- 账户余额表
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into account_tbl (user_id, money) VALUES (1, 1000);
insert into account_tbl (user_id, money) VALUES (2, 2000);
insert into account_tbl (user_id, money) VALUES (3, 3000);
1.3 脚手架准备
本文中使用Aliyun提供的脚手架
输入group和项目名后,下一步选择组件依赖
组建依赖:
- Spring Cloud Alibaba Seata
- Nacos Service Discovery
- Arthas
- Spring boot devtools
- Lombok
- Spring Configuration Processor
- MyBatis Plus Framework
- Validation
- Junit
- Spring web
可以初步浏览代码
点击获取代码即可将代码获取下来
1.4 Seata安装
官方安装教程:seata.io/zh-cn/docs/…
- 下载Seata Server,下载地址 github.com/seata/seata…
-
拉到最下面,选择压缩包下载并解压
-
进入到bin目录,执行以下命令即可启动seata,默认端口是8091
$ sh ./bin/seata-server.sh
- 启动成功后如下
1.5 Nacos 启动
Nacos 安装就不在此赘述了,进入nacos的bin目录执行以下命令
$ sh startup.sh -m standalone
启动成功后如下
控制台地址是:http://localhost:8848/nacos/
1.6 Seata 整合 Nacos
Seata如果使用Nacos作为配置中心,则需要进行相关的配置修改。其中registry.conf配置的是注册中心和配置中心的方式,默认是file,本文中将使用nacos作为配置中心和注册中心。
- 进入 Seata的conf目录,修改registry.conf 文件内容
- 将 type 由file修改成nacos,并配置nacos 的用户名和密码。注意registry和config都需要修改成nacos
- 修改file.conf文件内容
将type改成db, 配置本机数据的用户名密码
- 下载nacos config
在github.com/seata/seata… 中下载nacos-config.sh,并放到conf目录下
在github.com/seata/seata… 中下载config.txt,并放到根目录下,注意不是conf目录
- 修改config.txt文件
在conf目录下执行sh nacos-config.sh 127.0.0.1
,其中127.0.0.1是nacos地址
然后查看nacos配置中心,发现配置都发布到nacos上了
- 最终的registry.conf文件
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
apollo {
appId = "seata-server"
## apolloConfigService will cover apolloMeta
apolloMeta = "http://192.168.1.204:8801"
apolloConfigService = "http://192.168.1.204:8080"
namespace = "application"
apolloAccesskeySecret = ""
cluster = "seata"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
nodePath = "/seata/seata.properties"
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
- 最终的file.conf文件
service {
#transaction service group mapping
vgroupMapping.seata-demo-account="default"
vgroupMapping.seata-demo-order="default"
vgroupMapping.seata-demo-storage="default"
default.grouplist="127.0.0.1:8091"
disableGlobalTransaction=false
}
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## rsa decryption public key
publicKey = ""
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://127.0.0.1:3306/spring_cloud_alibaba?rewriteBatchedStatements=true"
user = "root"
password = "123456"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
## redis store property
redis {
## redis mode: single、sentinel
mode = "single"
## single mode property
single {
host = "127.0.0.1"
port = "6379"
}
## sentinel mode property
sentinel {
masterName = ""
## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
sentinelHosts = ""
}
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
- 最终的config.txt
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=true
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
service.vgroupMapping.seata-demo-storage=default
service.vgroupMapping.seata-demo-order=default
service.vgroupMapping.seata-demo-account=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
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=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
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
store.mode=file
store.lock.mode=file
store.session.mode=file
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/spring_cloud_alibaba?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
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
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
- 启动Seata
在bin目录下执行./seata-server.sh -h 127.0.0.1 -p 8091 -m db
,其中8091是Seata Server端口
1.7 项目配置
- 将上面的file.conf和registry.conf复制到每个项目的resource目录下
- 修改application.properties,添加nacos配置和数据源配置
# 应用名称
spring.application.name=seata-demo-storage
# 应用服务 WEB 访问端口
server.port=8080
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public
spring.cloud.alibaba.seata.tx-service-group=seata-demo-storage
# datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cloud_alibaba?useUnicode=true&&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
- 启动项目,项目启动成功后,会打印如下日志。至此我们才完成了基本的准备工作,接下来就是业务逻辑开发了。
2021-08-13 17:14:56.131 INFO 81427 --- [ restartedMain] c.a.c.n.registry.NacosServiceRegistry : nacos registry, DEFAULT_GROUP seata-demo-storage 172.18.38.14:8080 register finished
2021-08-13 17:14:56.156 INFO 81427 --- [ restartedMain] c.e.s.SeataDemoStorageApplication : Started SeataDemoStorageApplication in 13.745 seconds (JVM running for 17.071)
2. 架构设计
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
3. seata-demo-account 账户服务
3.1 AccountController
/**
* @ClassName : AccountController
* @Description : 账户
* @Author : zhaowenyi
* @Date: 2021/08/13
*/
@RestController
public class AccountController {
@Autowired
AccountTblService accountTblService;
@PostMapping("/api/account/debit")
public void debit(@RequestParam("userId") String userId,
@RequestParam("money") int money) {
accountTblService.debit(userId, money);
}
}
3.2 AccountController
/**
* 账户
*/
@Service
public class AccountTblServiceImpl extends ServiceImpl<AccountTblMapper, AccountTbl>
implements AccountTblService {
@Override
public void debit(String userId, int money) {
if(StringUtils.equals(userId, "2")) {
throw new RuntimeException("模拟异常");
}
var wrapper = new LambdaUpdateWrapper<AccountTbl>();
wrapper.setSql("money = money - " + money)
.eq(AccountTbl::getUserId, userId);
this.update(wrapper);
}
}
4. seata-demo-storage 库存服务
4.1 StorageController
/**
* @ClassName : StorageController
* @Description : 库存服务
* @Author : zhaowenyi
* @Date: 2021/08/13
*/
@RestController
public class StorageController {
@Autowired
StorageTblService storageTblService;
@PostMapping(value = "/api/storage/debuct")
public void debuct(@RequestParam(value = "commodityCode", required = true) String commodityCode,
@RequestParam(value = "count", required = true) Integer count) {
storageTblService.deduct(commodityCode, count);
}
}
4.2 StorageTblServiceImpl
/**
* 库存
*/
@Service
public class StorageTblServiceImpl extends ServiceImpl<StorageTblMapper, StorageTbl>
implements StorageTblService {
@Override
public void deduct(String commodityCode, int count) {
var wrapper = new LambdaUpdateWrapper<StorageTbl>();
wrapper.setSql("count = count - " + count)
.eq(StorageTbl::getCommodityCode, commodityCode);
this.update(wrapper);
}
}
5. seata-demo-storage 库存服务
5.1 OrderController
/**
* @ClassName : OrderController
* @Description : 订单服务
* @Author : zhaowenyi
* @Date: 2021/08/13
*/
@RestController
public class OrderController {
@Autowired
OrderTblService orderTblService;
@PostMapping("api/order/create")
public void create(String userId, String commodityCode, int orderCount) {
orderTblService.create(userId, commodityCode, orderCount);
}
}
5.2 OrderTblServiceImpl
要实现全局事务,在创建订单的时候加上@GlobalTransactional
注解即可
/**
* 订单服务
*/
@Service
public class OrderTblServiceImpl extends ServiceImpl<OrderTblMapper, OrderTbl>
implements OrderTblService {
@Autowired
AccountFeignClient accountFeignClient;
@Autowired
StorageFeignClient storageFeignClient;
@GlobalTransactional
@Override
public OrderTbl create(String userId, String commodityCode, int orderCount) {
OrderTbl order = new OrderTbl();
order.setCommodityCode(commodityCode);
order.setCount(orderCount);
order.setMoney(orderCount * 100);
order.setUserId(userId);
this.save(order);
// 扣减余额
accountFeignClient.debit(userId, order.getMoney());
// 扣减库存
storageFeignClient.debuct(commodityCode, orderCount);
return null;
}
}
6. 测试
6.1 注释@GlobalTransactional注解
- 初始化的数据
- 模拟正常情况,用户1,购买商品product_001
curl --location --request POST 'localhost:8082/api/order/create?userId=1&orderCount=1&commodityCode=product_001' \
--header 'Cookie: JSESSIONID=BEA387BFD6640DE11364C8A10C11A85C'
订单表多了一条数据
账户表少了100
库存表少了1
- 模拟异常情况,用户2,购买商品product_001
调用扣划账户余额失败
feign.FeignException$InternalServerError: [500] during [POST] to [http://seata-demo-account/api/account/debit?userId=2&money=100] [AccountFeignClient#debit(String,int)]: [{"timestamp":"2021-08-13T12:11:38.571+00:00","status":500,"error":"Internal Server Error","trace":"java.lang.RuntimeException: 模拟异常\n\tat com.elio.seatademoaccount.impl.AccountTblServiceImpl.debit(Acc... (5528 bytes)]
at feign.FeignException.serverErrorStatus(FeignException.java:231) ~[feign-core-10.10.1.jar:na]
at feign.FeignException.errorStatus(FeignException.java:180) ~[feign-core-10.10.1.jar:na]
at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.10.1.jar:na]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.10.1.jar:na]
at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:96) ~[feign-core-10.10.1.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.10.1.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.10.1.jar:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.10.1.jar:na]
at com.sun.proxy.$Proxy98.debit(Unknown Source) ~[na:na]
at com.elio.seatademoorder.impl.OrderTblServiceImpl.create(OrderTblServiceImpl.java:38) ~[classes/:na]
订单表多了一条数据
账户表此时没有扣钱!!!
库存表也没有扣减库存!!!
这说明创建订单服务不是原子操作,创建订单成功,扣减账户余额和库存都失败了。
6.2 加上@GlobalTransactional注解
当在创建订单时加上@GlobalTransactional
注解,模拟异常情况
订单表此时没有创建新的订单
此时账户也没有扣减余额
在看下日志,在调用扣划账户余额失败,Seata对订单服务进行了回滚
2021-08-13 20:17:01.110 INFO 91060 --- [ch_RMROLE_1_1_8] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid=172.18.41.122:8091:7458126500631044112,branchId=7458126500631044115,branchType=AT,resourceId=jdbc:mysql://localhost:3306/spring_cloud_alibaba,applicationData=null
2021-08-13 20:17:01.112 INFO 91060 --- [ch_RMROLE_1_1_8] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 172.18.41.122:8091:7458126500631044112 7458126500631044115 jdbc:mysql://localhost:3306/spring_cloud_alibaba
2021-08-13 20:17:01.196 INFO 91060 --- [ch_RMROLE_1_1_8] i.s.r.d.undo.AbstractUndoLogManager : xid 172.18.41.122:8091:7458126500631044112 branch 7458126500631044115, undo_log deleted with GlobalFinished
2021-08-13 20:17:01.198 INFO 91060 --- [ch_RMROLE_1_1_8] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
2021-08-13 20:17:01.224 INFO 91060 --- [nio-8082-exec-2] i.seata.tm.api.DefaultGlobalTransaction : [172.18.41.122:8091:7458126500631044112] rollback status: Rollbacked
2021-08-13 20:17:01.263 ERROR 91060 --- [nio-8082-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$InternalServerError: [500] during [POST] to [http://seata-demo-account/api/account/debit?userId=2&money=100] [AccountFeignClient#debit(String,int)]: [{"timestamp":"2021-08-13T12:17:01.050+00:00","status":500,"error":"Internal Server Error","trace":"java.lang.RuntimeException: 模拟异常\n\tat com.elio.seatademoaccount.impl.AccountTblServiceImpl.debit(Acc... (5528 bytes)]] with root cause
feign.FeignException$InternalServerError: [500] during [POST] to [http://seata-demo-account/api/account/debit?userId=2&money=100] [AccountFeignClient#debit(String,int)]: [{"timestamp":"2021-08-13T12:17:01.050+00:00","status":500,"error":"Internal Server Error","trace":"java.lang.RuntimeException: 模拟异常\n\tat com.elio.seatademoaccount.impl.AccountTblServiceImpl.debit(Acc... (5528 bytes)]
at feign.FeignException.serverErrorStatus(FeignException.java:231) ~[feign-core-10.10.1.jar:na]
at feign.FeignException.errorStatus(FeignException.java:180) ~[feign-core-10.10.1.jar:na]
at feign.FeignException.errorStatus(FeignException.java:169) ~[feign-core-10.10.1.jar:na]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:92) ~[feign-core-10.10.1.jar:na]
at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:96) ~[feign-core-10.10.1.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.10.1.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:89) ~[feign-core-10.10.1.jar:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100) ~[feign-core-10.10.1.jar:na]
at com.sun.proxy.$Proxy99.debit(Unknown Source) ~[na:na]
at com.elio.seatademoorder.impl.OrderTblServiceImpl.create(OrderTblServiceImpl.java:38) ~[classes/:na]
总结
本文通过电商中很简单的一个案例给大家展示了Seata的厉害,除了在一开始安装Seata时花费了很多时间,后续的分布式事务只需要加上一个@GlobalTransactional
注解接口,真正的做到了无业务入侵,并且方便快捷。但是Seata是如何做到分布式事务的控制呢,对我们来说还是个黑盒。笔者将在接下来的文章中深入探讨Seata的工作原理