Seata架构篇 - SAGA模式
创始人
2025-05-29 03:05:40

SAGA模式

Saga 模式是 Seata 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当某个参与者出现失败,则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都有业务开发实现。

Saga模式示意图

适用场景

业务流程长、业务流程多。

参与者包含其它公司或者遗留系统服务,无法提供 TCC 模式要求的三个接口。

原理

SEATA提供的Saga模式是基于状态机引擎来实现的,机制是:

  1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
  2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
  3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚

注意: 异常发生时是否进行补偿也可由用户自定义决定

  1. 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

使用

@GetMapping("/test_saga")
public String testSaga() {Map startParams = new HashMap<>(3);String businessKey = String.valueOf(System.currentTimeMillis());startParams.put("businessKey", businessKey);startParams.put("count", 10);startParams.put("amount", new BigDecimal("100"));// 模拟compensate//startParams.put("mockReduceBalanceFail", "true");StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("reduceInventoryAndBalance", null,businessKey, startParams);Assert.isTrue(ExecutionStatus.SU.equals(inst.getStatus()),"saga transaction execute failed. XID: " + inst.getId());System.out.println("saga transaction commit succeed. XID: " + inst.getId());return "finished";
}

在 resources/statelang 目录下创建一个 reduce_inventory_and_balance.json 文件。

{"Name": "reduceInventoryAndBalance","Comment": "reduce inventory then reduce balance in a transaction","StartState": "ReduceInventory","Version": "0.0.1","States": {"ReduceInventory": {"Type": "ServiceTask","ServiceName": "inventoryAction","ServiceMethod": "reduce","CompensateState": "CompensateReduceInventory","Next": "ChoiceState","Input": ["$.[businessKey]","$.[count]"],"Output": {"reduceInventoryResult": "$.#root"},"Status": {"#root == true": "SU","#root == false": "FA","$Exception{java.lang.Throwable}": "UN"}},"ChoiceState": {"Type": "Choice","Choices": [{"Expression": "[reduceInventoryResult] == true","Next": "ReduceBalance"}],"Default": "Fail"},"ReduceBalance": {"Type": "ServiceTask","ServiceName": "balanceAction","ServiceMethod": "reduce","CompensateState": "CompensateReduceBalance","Input": ["$.[businessKey]","$.[amount]",{"throwException": "$.[mockReduceBalanceFail]"}],"Output": {"compensateReduceBalanceResult": "$.#root"},"Status": {"#root == true": "SU","#root == false": "FA","$Exception{java.lang.Throwable}": "UN"},"Catch": [{"Exceptions": ["java.lang.Throwable"],"Next": "CompensationTrigger"}],"Next": "Succeed"},"CompensateReduceInventory": {"Type": "ServiceTask","ServiceName": "inventoryAction","ServiceMethod": "compensateReduce","Input": ["$.[businessKey]"]},"CompensateReduceBalance": {"Type": "ServiceTask","ServiceName": "balanceAction","ServiceMethod": "compensateReduce","Input": ["$.[businessKey]"]},"CompensationTrigger": {"Type": "CompensationTrigger","Next": "Fail"},"Succeed": {"Type": "Succeed"},"Fail": {"Type": "Fail","ErrorCode": "PURCHASE_FAILED","Message": "purchase failed"}}
}

BalanceAction

public interface BalanceAction {boolean reduce(String businessKey, BigDecimal amount, Map params);boolean compensateReduce(String businessKey, Map params);}

BalanceActionImpl

@Service("balanceAction")
public class BalanceActionImpl implements BalanceAction {private static final Logger LOGGER = LoggerFactory.getLogger(BalanceActionImpl.class);@Overridepublic boolean reduce(String businessKey, BigDecimal amount, Map params) {if(params != null) {Object throwException = params.get("throwException");if (throwException != null && "true".equals(throwException.toString())) {throw new RuntimeException("reduce balance failed");}}LOGGER.info("reduce balance succeed, amount: " + amount + ", businessKey:" + businessKey);return true;}@Overridepublic boolean compensateReduce(String businessKey, Map params) {if(params != null) {Object throwException = params.get("throwException");if (throwException != null && "true".equals(throwException.toString())) {throw new RuntimeException("compensate reduce balance failed");}}LOGGER.info("compensate reduce balance succeed, businessKey:" + businessKey);return true;}
}

InventoryAction

public interface InventoryAction {boolean reduce(String businessKey, int count);boolean compensateReduce(String businessKey);
}

InventoryActionImpl

@Service("inventoryAction")
public class InventoryActionImpl implements InventoryAction {private static final Logger LOGGER = LoggerFactory.getLogger(InventoryActionImpl.class);@Overridepublic boolean reduce(String businessKey, int count) {LOGGER.info("reduce inventory succeed, count: " + count + ", businessKey:" + businessKey);return true;}@Overridepublic boolean compensateReduce(String businessKey) {LOGGER.info("compensate reduce inventory succeed, businessKey:" + businessKey);return true;}
}

ITAccountService

public interface ITAccountService extends IService {ObjectResponse decreaseAccount(AccountDTO accountDTO);void testGlobalLock();
}

TAccountServiceImpl

@Service
public class TAccountServiceImpl extends ServiceImpl implements ITAccountService {@Overridepublic ObjectResponse decreaseAccount(AccountDTO accountDTO) {int account = baseMapper.decreaseAccount(accountDTO.getUserId(), accountDTO.getAmount().doubleValue());ObjectResponse response = new ObjectResponse<>();if (account > 0) {response.setStatus(RspStatusEnum.SUCCESS.getCode());response.setMessage(RspStatusEnum.SUCCESS.getMessage());return response;}response.setStatus(RspStatusEnum.FAIL.getCode());response.setMessage(RspStatusEnum.FAIL.getMessage());return response;}@Override@GlobalLock@Transactional(rollbackFor = {Throwable.class})public void testGlobalLock() {baseMapper.testGlobalLock("1");System.out.println("Hi, i got lock, i will do some thing with holding this lock.");}
}
                
            
            
            

相关内容

热门资讯

双鸭山市公园哪个最好人气高 在双鸭山市,公园是市民休闲娱乐、亲近自然的好去处。每个公园都有其独特的魅力,而人气高的公园往往是集美...
明山区公园哪个最好人气高 在明山区,公园是人们休闲放松、亲近自然的好去处。不同的公园有着各自独特的魅力,吸引着众多游客前往。那...
兰卡斯特连续第五年庆春节 舞龙... 2026年2月8日,英国西北部城市兰卡斯特举行中国春节庆祝活动,成为英国率先庆祝马年新春的城市之一。...
清流县周边亲子游景点推荐 在忙碌的生活中,亲子游成为了增进家庭感情、开阔孩子视野的绝佳方式。清流县周边拥有众多适合亲子游玩的景...
周新强主持召开全市春节文旅工作... 2月13日,市长周新强主持召开全市春节文旅工作调度会,安排部署春节期间文旅引流及服务保障工作。副市长...