之前笔记整理过AT模式:分布式事务seata(AT)与nacos整合-笔记2-CSDN博客
对于TCC模式是类似的。相对于AT更灵活。
1 TCC 模式原理
官方介绍:
这个介绍比较简单,demo可以快速体验下。
实际落地根据自己业务模式来考虑,核心关注点有3个:幂等、允许空回滚、防悬挂控制。
对于用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。
比如之前的扣钱,不能直接扣钱,拆成两阶段,实现成三个方法,并且保证一阶段 Try 成功的话 二阶段 Confirm 一定能成功。
一阶段:
TRY: 冻结部分扣款。
二阶段:
confirm: 真正扣款,把冻结金额清除。
cancel:释放一阶段 Try 冻结的 金额,还原回去。
类似的扣库存也是一个道理。但是对于下单就不好直接操作,需要靠逻辑的状态预下单等方式变通处理。
2 demo 快速验证
demo 没有考虑幂等、允许空回滚、防悬挂控制这些因素。基于之前的工程临时加了个表
CREATE TABLE `storage_freeze` (
`xid` varchar(250) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '事务id',
`commodity_code` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '用户id',
`freeze_storage` int unsigned DEFAULT '0' COMMENT '冻结库存',
`state` int DEFAULT NULL COMMENT '事务状态,1:try,0:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;
还可以再原表上加冻结字段来表示。都可以。仅作为示意。
对比 AT改动点:
2.1发起方:
order 下单接口,仅改变Feign的接口url
//at
@PostMapping("/storage/reduce-stock")
Result<?> reduceStock(@RequestBody StorageDTO productReduceStockDTO);
// tcc
@PostMapping("/storage/prepare")
Result<?> prepare(@RequestBody StorageDTO productReduceStockDTO);
服务发起调用方order注解:@GlobalTransactional 不变。
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.tuling.tlmallorder.service.impl;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import org.tuling.tlmallcommon.BusinessException;
import org.tuling.tlmallcommon.Result;
import org.tuling.tlmallorder.entity.Order;
import org.tuling.tlmallorder.feign.AccountServiceFeignClient;
import org.tuling.tlmallorder.feign.StorageServiceFeignClient;
import org.tuling.tlmallorder.feign.dto.AccountDTO;
import org.tuling.tlmallorder.feign.dto.StorageDTO;
import org.tuling.tlmallorder.mapper.OrderMapper;
import org.tuling.tlmallorder.service.OrderService;
import java.sql.Timestamp;
import java.util.List;
import static org.tuling.tlmallcommon.ResultEnum.COMMON_FAILED;
@Service
public class OrderServiceImpl implements OrderService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountServiceFeignClient accountService;
@Autowired
private StorageServiceFeignClient storageService;
@Autowired
RestTemplate restTemplate;
@Override
@GlobalTransactional(name="createOrder")
public Result<?> createOrder(String userId, String commodityCode, Integer count) {
logger.info("[createOrder] current XID: {}", RootContext.getXID());
// deduct storage
StorageDTO storageDTO = new StorageDTO();
storageDTO.setCommodityCode(commodityCode);
storageDTO.setCount(count);
//RestTemplate远程调用
//String storage_url = "http://localhost:8010/storage/reduce-stock";
//整合了Nacos+LoadBalaner,可以使用微服务名tlmall-storage代替localhost:8020
//String storage_url = "http://tlmall-storage/storage/reduce-stock";
//Integer storageCode = restTemplate.postForObject(storage_url,storageDTO, Result.class).getCode();
//openFeign远程调用
// Integer storageCode = storageService.reduceStock(storageDTO).getCode();
Integer storageCode = storageService.prepare(storageDTO).getCode();
if (storageCode.equals(COMMON_FAILED.getCode())) {
throw new BusinessException("stock not enough");
}
// deduct balance
int price = count * 2;
AccountDTO accountDTO = new AccountDTO();
accountDTO.setUserId(userId);
accountDTO.setPrice(price);
//RestTemplate远程调用
//String account_url = "http://localhost:8020/account/reduce-balance";
//整合了Nacos+LoadBalaner,可以使用微服务名tlmall-account代替localhost:8020
//String account_url = "http://tlmall-account/account/reduce-balance";
//Integer accountCode = restTemplate.postForObject(account_url, accountDTO, Result.class).getCode();
//openFeign远程调用
// Integer accountCode = accountService.reduceBalance(accountDTO).getCode();
Integer accountCode = accountService.prepare(accountDTO).getCode();
if (accountCode.equals(COMMON_FAILED.getCode())) {
throw new BusinessException("balance not enough");
}
// save order
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setMoney(price);
order.setCreateTime(new Timestamp(System.currentTimeMillis()));
order.setUpdateTime(new Timestamp(System.currentTimeMillis()));
orderMapper.saveOrder(order);
logger.info("[createOrder] orderId: {}", order.getId());
return Result.success(order);
}
@Override
public Result<?> getOrderByUserId(String userId) {
List<Order> list = orderMapper.getOrderByUserId(userId);
return Result.success(list);
}
}
2.2 被调用方举例(account服务):
AccountController 提供新的入口
@PostMapping("/prepare")
public Result<?> prepareReduceBalance(@RequestBody AccountDTO accountDTO) {
try {
tccAccountService.prepare(accountDTO.getUserId(), accountDTO.getPrice());
}
catch (BusinessException e) {
return Result.failed(e.getMessage());
}
return Result.success("");
}
服务:
接口:
package org.tuling.tlmallaccount.service;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import org.tuling.tlmallcommon.BusinessException;
@LocalTCC
public interface TCCAccountService {
/**
*
* @param userId
* @param price
* @throws BusinessException
*/
@TwoPhaseBusinessAction(name="prepare",commitMethod = "commit",rollbackMethod = "rollback")
void prepare(String userId, Integer price) throws BusinessException;
/**
* 二阶段提交
* @param businessActionContext
* @return
*/
public boolean commit(BusinessActionContext businessActionContext);
/**
* 二阶段回滚
* @param businessActionContext
* @return
*/
public boolean rollback(BusinessActionContext businessActionContext);
}
对应具体接口实现
package org.tuling.tlmallaccount.service.impl;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.tuling.tlmallaccount.dto.AccountFreeze;
import org.tuling.tlmallaccount.mapper.AccountFreezeMapper;
import org.tuling.tlmallaccount.mapper.AccountMapper;
import org.tuling.tlmallaccount.service.TCCAccountService;
import org.tuling.tlmallcommon.BusinessException;
import java.sql.Timestamp;
@Service
public class TCCAccountServiceImpl implements TCCAccountService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
@Transactional
public void prepare(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "price") Integer price) throws BusinessException {
String xid = RootContext.getXID();
logger.info("account [prepareReduce] current XID: "+xid+",price:"+price);
// 1.幂等处理
// if (TccActionHandler.hasPrepareResult(xid)) {
// return ;
// }
// 2.防止悬挂,已经执行过回滚了就不能再预留资源
// if (TccActionHandler.hasRollbackResult(xid) || TccActionHandler.hasCommitResult(xid)) {
// return ;
// }
//减钱
Timestamp updateTime = new Timestamp(System.currentTimeMillis());
int updateCount = accountMapper.reduceBalance(userId, price, updateTime);
if (updateCount == 0) {
throw new BusinessException("reduce balance failed");
}
//记录冻结金额
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(price);
// 1 表示 try 状态,0 表示 cancel 状态
freeze.setState(1);
freeze.setXid(xid);
int insert =freezeMapper.insert(freeze);
// 4.更新分支事务Try状态,执行成功
// TccActionHandler.prepareSuccess(RootContext.getXID());
logger.info("account [prepare] over,xid={},insert:{}",xid,insert);
}
//事务成功提交的方法
@Override
@Transactional
public boolean commit(BusinessActionContext businessActionContext) {
// 1.幂等处理
// if (TccActionHandler.hasCommitResult(xid)) {
// return true;
// }
//获取事务id
String xid = businessActionContext.getXid();
logger.info("account commit ,xid:{}",xid);
//根据id删除冻结记录
int count = freezeMapper.deleteById(xid);
logger.info("account [commit] over,xid={},delete:{}",xid,count);
// 3.更新分支事务Try状态,执行成功
// TccActionHandler.commitSuccess(businessActionContext.getXid());
return true;
}
@Override
@Transactional
public boolean rollback(BusinessActionContext businessActionContext) {
//通过事务id查询冻结记录中的金额
String xid = businessActionContext.getXid();
logger.info("account rollback ,xid:{}",xid);
// 1.幂等处理
// if (TccActionHandler.hasRollbackResult(xid)) {
// return true;
// }
// // 2.没有预留资源结果,回滚不做任何处理;
// if (!TccActionHandler.hasPrepareResult(xid)) {
// // 设置回滚结果,防止空悬挂
// TccActionHandler.rollbackResult(xid);
// return true;
// }
AccountFreeze freeze = freezeMapper.selectById(xid);
if (freeze == null) {
logger.info("空回滚,xid:{}",xid);
return true;
}
//如果获取到的冻结记录,状态本身已经是 cancel 状态,则不再进行处理
if (freeze.getState() == 0) {
return true;
}
//恢复余额
Timestamp updateTime = new Timestamp(System.currentTimeMillis());
accountMapper.addBalance(freeze.getUserId(), freeze.getFreezeMoney(),updateTime);
//将冻结金额清零,状态改为 cancel
//1 表示 try 状态,0 表示 cancel 状态
freeze.setFreezeMoney(0);
freeze.setState(0);
freezeMapper.updateById(freeze);
// 设置回滚结果
// TccActionHandler.rollbackResult(xid);
return true;
}
}
这里需要注意的是每个方法都必须声明 @Transactional,接口层要有注解
其中对外暴漏的事服务的入口prepare方法,提交、回滚方法seata框架自己会调用。
类似的库存服务就不贴了。
2.3 验证
调用下单接口,看下成果日志:能看到模式变成TCC,能正常提交
//account日志
2024-12-16T11:33:21.065+08:00 INFO 26540 --- [tlmall-account] [nio-8020-exec-1] io.seata.rm.AbstractResourceManager : branch register success, xid:192.168.59.96:8091:7566644201535903765, branchId:7566644201535903785, lockKeys:null
2024-12-16T11:33:21.165+08:00 INFO 26540 --- [tlmall-account] [nio-8020-exec-1] o.t.t.s.impl.TCCAccountServiceImpl : account [prepareReduce] current XID: 192.168.59.96:8091:7566644201535903765,price:2
2024-12-16T11:33:21.480+08:00 INFO 26540 --- [tlmall-account] [nio-8020-exec-1] o.t.t.s.impl.TCCAccountServiceImpl : account [prepare] over,xid=192.168.59.96:8091:7566644201535903765,insert:1
2024-12-16T11:33:24.110+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchCommitProcessor : rm client handle branch commit process:BranchCommitRequest{xid='192.168.59.96:8091:7566644201535903765', branchId=7566644201535903785, branchType=TCC, resourceId='prepare', applicationData='{"actionContext":{"action-start-time":1734320000765,"useTCCFence":false,"sys::prepare":"prepare","price":2,"sys::rollback":"rollback","sys::commit":"commit","host-name":"192.168.59.96","userId":"fox","actionName":"prepare"}}'}
2024-12-16T11:33:24.116+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.59.96:8091:7566644201535903765 7566644201535903785 prepare {"actionContext":{"action-start-time":1734320000765,"useTCCFence":false,"sys::prepare":"prepare","price":2,"sys::rollback":"rollback","sys::commit":"commit","host-name":"192.168.59.96","userId":"fox","actionName":"prepare"}}
2024-12-16T11:33:24.223+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_1_16] o.t.t.s.impl.TCCAccountServiceImpl : account commit ,xid:192.168.59.96:8091:7566644201535903765
2024-12-16T11:33:24.294+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_1_16] o.t.t.s.impl.TCCAccountServiceImpl : account [commit] over,xid=192.168.59.96:8091:7566644201535903765,delete:1
2024-12-16T11:33:24.359+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_1_16] io.seata.rm.AbstractResourceManager : TCC resource commit result : true, xid: 192.168.59.96:8091:7566644201535903765, branchId: 7566644201535903785, resourceId: prepare
2024-12-16T11:33:24.360+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
扣库存日志
2024-12-16T11:33:18.319+08:00 INFO 21792 --- [tlmall-storage] [nio-8010-exec-1] io.seata.rm.AbstractResourceManager : branch register success, xid:192.168.59.96:8091:7566644201535903765, branchId:7566644201535903776, lockKeys:null
2024-12-16T11:33:19.082+08:00 INFO 21792 --- [tlmall-storage] [nio-8010-exec-1] o.t.t.service.impl.StorageServiceImpl : storage [prepare] current XID: 192.168.59.96:8091:7566644201535903765,orderCount:1
2024-12-16T11:33:19.233+08:00 INFO 21792 --- [tlmall-storage] [nio-8010-exec-1] o.t.t.service.impl.StorageServiceImpl : storage [prepare] over,xid=192.168.59.96:8091:7566644201535903765,insert:1
2024-12-16T11:33:23.850+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_6_16] i.s.c.r.p.c.RmBranchCommitProcessor : rm client handle branch commit process:BranchCommitRequest{xid='192.168.59.96:8091:7566644201535903765', branchId=7566644201535903776, branchType=TCC, resourceId='prepare', applicationData='{"actionContext":{"action-start-time":1734319998226,"useTCCFence":false,"sys::prepare":"prepare","commodityCode":"1","orderCount":1,"sys::rollback":"rollback","sys::commit":"commit","host-name":"192.168.59.96","actionName":"prepare"}}'}
2024-12-16T11:33:23.850+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_6_16] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.59.96:8091:7566644201535903765 7566644201535903776 prepare {"actionContext":{"action-start-time":1734319998226,"useTCCFence":false,"sys::prepare":"prepare","commodityCode":"1","orderCount":1,"sys::rollback":"rollback","sys::commit":"commit","host-name":"192.168.59.96","actionName":"prepare"}}
2024-12-16T11:33:23.918+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_6_16] o.t.t.service.impl.StorageServiceImpl : storage commit ,xid:192.168.59.96:8091:7566644201535903765
2024-12-16T11:33:24.057+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_6_16] io.seata.rm.AbstractResourceManager : TCC resource commit result : true, xid: 192.168.59.96:8091:7566644201535903765, branchId: 7566644201535903776, resourceId: prepare
2024-12-16T11:33:24.057+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_6_16] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
模拟当库存够但是余额不足的情况引发的回滚
2024-12-16T11:43:31.700+08:00 INFO 26540 --- [tlmall-account] [nio-8020-exec-2] io.seata.rm.AbstractResourceManager : branch register success, xid:192.168.59.96:8091:7566644201535903800, branchId:7566644201535903825, lockKeys:null
2024-12-16T11:43:31.773+08:00 INFO 26540 --- [tlmall-account] [nio-8020-exec-2] o.t.t.s.impl.TCCAccountServiceImpl : account [prepareReduce] current XID: 192.168.59.96:8091:7566644201535903800,price:2
2024-12-16T11:43:32.021+08:00 ERROR 26540 --- [tlmall-account] [nio-8020-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.RuntimeException: try to proceed invocation error] with root cause
org.tuling.tlmallcommon.BusinessException: reduce balance failed
at org.tuling.tlmallaccount.service.impl.TCCAccountServiceImpl.prepare(TCCAccountServiceImpl.java:40) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at io.seata.spring.annotation.AdapterInvocationWrapper.proceed(AdapterInvocationWrapper.java:57) ~[seata-all-2.0.0.jar:2.0.0]
at io.seata.integration.tx.api.interceptor.ActionInterceptorHandler.proceed(ActionInterceptorHandler.java:104) ~[seata-all-2.0.0.jar:2.0.0]
at io.seata.rm.tcc.interceptor.TccActionInterceptorHandler.doInvoke(TccActionInterceptorHandler.java:97) ~[seata-all-2.0.0.jar:2.0.0]
at io.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler.invoke(AbstractProxyInvocationHandler.java:35) ~[seata-all-2.0.0.jar:2.0.0]
at io.seata.spring.annotation.AdapterSpringSeataInterceptor.invoke(AdapterSpringSeataInterceptor.java:45) ~[seata-all-2.0.0.jar:2.0.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717) ~[spring-aop-6.1.5.jar:6.1.5]
at org.tuling.tlmallaccount.service.impl.TCCAccountServiceImpl$$SpringCGLIB$$0.prepare(<generated>) ~[classes/:na]
at org.tuling.tlmallaccount.controller.AccountController.prepareReduceBalance(AccountController.java:60) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255) ~[spring-web-6.1.5.jar:6.1.5]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188) ~[spring-web-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:925) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:830) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.5.jar:6.1.5]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.1.5.jar:6.1.5]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.19.jar:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.5.jar:6.1.5]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.19.jar:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.5.jar:6.1.5]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.5.jar:6.1.5]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109) ~[spring-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.5.jar:6.1.5]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.5.jar:6.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.5.jar:6.1.5]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.StandardHostValve.$sw$original$invoke$005sj03(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.StandardHostValve.$sw$original$invoke$005sj03$accessor$$sw$p8ebm33(StandardHostValve.java) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.StandardHostValve$$sw$auxiliary$8iqdhg0.call(Unknown Source) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86) ~[skywalking-agent.jar:9.3.0]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
2024-12-16T11:43:33.440+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_2_16] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='192.168.59.96:8091:7566644201535903800', branchId=7566644201535903825, branchType=TCC, resourceId='prepare', applicationData='{"actionContext":{"action-start-time":1734320611609,"useTCCFence":false,"sys::prepare":"prepare","price":2,"sys::rollback":"rollback","sys::commit":"commit","host-name":"192.168.59.96","userId":"fox","actionName":"prepare"}}'}
2024-12-16T11:43:33.446+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_2_16] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 192.168.59.96:8091:7566644201535903800 7566644201535903825 prepare
2024-12-16T11:43:33.527+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_2_16] o.t.t.s.impl.TCCAccountServiceImpl : account rollback ,xid:192.168.59.96:8091:7566644201535903800
account rollback ,xid:192.168.59.96:8091:7566644201535903800
2024-12-16T11:43:33.596+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_2_16] o.t.t.s.impl.TCCAccountServiceImpl : 空回滚,xid:192.168.59.96:8091:7566644201535903800
2024-12-16T11:43:33.666+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_2_16] io.seata.rm.AbstractResourceManager : TCC resource rollback result : true, xid: 192.168.59.96:8091:7566644201535903800, branchId: 7566644201535903825, resourceId: prepare
2024-12-16T11:43:33.666+08:00 INFO 26540 --- [tlmall-account] [h_RMROLE_1_2_16] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
2024-12-16T11:43:26.426+08:00 INFO 21792 --- [tlmall-storage] [nio-8010-exec-2] io.seata.rm.AbstractResourceManager : branch register success, xid:192.168.59.96:8091:7566644201535903800, branchId:7566644201535903809, lockKeys:null
2024-12-16T11:43:27.465+08:00 INFO 21792 --- [tlmall-storage] [nio-8010-exec-2] o.t.t.service.impl.StorageServiceImpl : storage [prepare] current XID: 192.168.59.96:8091:7566644201535903800,orderCount:1
2024-12-16T11:43:27.634+08:00 INFO 21792 --- [tlmall-storage] [nio-8010-exec-2] o.t.t.service.impl.StorageServiceImpl : storage [prepare] over,xid=192.168.59.96:8091:7566644201535903800,insert:1
2024-12-16T11:43:32.525+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_7_16] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:BranchRollbackRequest{xid='192.168.59.96:8091:7566644201535903800', branchId=7566644201535903809, branchType=TCC, resourceId='prepare', applicationData='{"actionContext":{"action-start-time":1734320606329,"useTCCFence":false,"sys::prepare":"prepare","commodityCode":"1","orderCount":1,"sys::rollback":"rollback","sys::commit":"commit","host-name":"192.168.59.96","actionName":"prepare"}}'}
2024-12-16T11:43:32.584+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 192.168.59.96:8091:7566644201535903800 7566644201535903809 prepare
2024-12-16T11:43:32.659+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_7_16] o.t.t.service.impl.StorageServiceImpl : storage rollback ,xid:192.168.59.96:8091:7566644201535903800
2024-12-16T11:43:33.377+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_7_16] io.seata.rm.AbstractResourceManager : TCC resource rollback result : true, xid: 192.168.59.96:8091:7566644201535903800, branchId: 7566644201535903809, resourceId: prepare
2024-12-16T11:43:33.378+08:00 INFO 21792 --- [tlmall-storage] [h_RMROLE_1_7_16] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
看到是触发回滚了。可以再结合数据表数据来验证回滚补偿
3总结
AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景。
TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,TCC 性能会比 AT 模式高很多。最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。适用于核心系统等对性能有很高要求的场景。
没有最好的,只有适合的。
标签:10.1,java,seata,19,demo,jar,6.1,org,TCC From: https://blog.csdn.net/bohu83/article/details/144502847