seata【SAGA模式】代码实践(细节未必完全符合saga的配置,仅参考)

seata

SAGA模式:

代码仍然是上一篇AT模式的代码:AT模式
不需要undo_log表

下面开始:

首先,saga模式依靠状态机的json文件来执行整个流程,其中的开始节点的服务即TM,然后状态机需要依靠三张表,:
seata_state_inst,seate_state_machine_def,seata_state_machine_inst
建表语句如下,注意,这三张表需要注册在你的TM服务所属的业务库,其余子业务库不需要,因为saga原则上是谁先开始TM,谁的库负责存状态机的相关信息:

-- ----------------------------
-- Table structure for seata_state_inst
-- ----------------------------
DROP TABLE IF EXISTS `seata_state_inst`;
CREATE TABLE `seata_state_inst`  (
  `id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
  `machine_inst_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'state machine instance id',
  `NAME` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'state name',
  `TYPE` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state type',
  `service_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'service name',
  `service_method` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'method name',
  `service_type` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'service type',
  `business_key` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'business key',
  `state_id_compensated_for` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state compensated for',
  `state_id_retried_for` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state retried for',
  `gmt_started` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'start time',
  `is_for_update` tinyint(1) DEFAULT NULL COMMENT 'is service for update',
  `input_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'input parameters',
  `output_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'output parameters',
  `STATUS` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
  `excep` blob COMMENT 'exception',
  `gmt_end` timestamp(3) NOT NULL COMMENT 'end time',
  PRIMARY KEY (`id`, `machine_inst_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for seata_state_machine_def
-- ----------------------------
DROP TABLE IF EXISTS `seata_state_machine_def`;
CREATE TABLE `seata_state_machine_def`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
  `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'name',
  `tenant_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'tenant id',
  `app_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'application name',
  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'state language type',
  `comment_` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'comment',
  `ver` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'version',
  `gmt_create` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'create time',
  `status` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'status(AC:active|IN:inactive)',
  `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'content',
  `recover_strategy` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'transaction recover strategy(compensate|retry)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for seata_state_machine_inst
-- ----------------------------
DROP TABLE IF EXISTS `seata_state_machine_inst`;
CREATE TABLE `seata_state_machine_inst`  (
  `id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
  `machine_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'state machine definition id',
  `tenant_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'tenant id',
  `parent_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'parent id',
  `gmt_started` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'start time',
  `business_key` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'business key',
  `start_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'start parameters',
  `gmt_end` timestamp(3) NOT NULL COMMENT 'end time',
  `excep` blob COMMENT 'exception',
  `end_params` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'end parameters',
  `STATUS` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
  `compensation_status` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
  `is_running` tinyint(1) DEFAULT NULL COMMENT 'is running(0 no|1 yes)',
  `gmt_updated` timestamp(3) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `unikey_buz_tenant`(`business_key`, `tenant_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

接下来是定义状态机json文件,这里官方推荐用那个状态机desinger,具体见官网吧,需要下下来npm install,npm start ,运行起来如图:
在这里插入图片描述

这里附上我的json文件,并简单解释json的程序走向

{
  "nodes": [
    {
      "type": "node",
      "size": "72*72",
      "shape": "flow-circle",
      "color": "#FA8C16",
      "label": "Start",
      "stateId": "Start",
      "stateType": "Start",
      "stateProps": {
        "StateMachine": {
          "Name": "startCreateOrder",
          "Comment": "开始下单",
          "Version": "0.0.1"
        },
        "Next": "ReduceGoods"
      },
      "x": 221.60527099609374,
      "y": -133.02889122009276,
      "id": "db4c4a01",
      "index": 6
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-rect",
      "color": "#1890FF",
      "label": "ReduceGoods",
      "stateId": "ReduceGoods",
      "stateType": "ServiceTask",
      "stateProps": {
        "Type": "ServiceTask",
        "ServiceName": "doCreateOrderOperation",
        "ServiceMethod": "reduceGoodsCount",
        "Next": "ChoiceGoodsState",
        "Input": [
          "$.[businessKey]",
          "$.[goodsId]"
        ],
        "Output": {
          "ReduceGoodsResult": "$.#root"
        },
        "Status": {
          "#root == true": "SU",
          "#root == false": "FA",
          "$Exception{java.lang.Throwable}": "UN"
        },
        "CompensateState": "ReduceGoodsCompensation"
      },
      "x": 221.60527099609374,
      "y": -5.02889122009276,
      "id": "ed9b4961",
      "index": 7
    },
    {
      "type": "node",
      "size": "80*72",
      "shape": "flow-rhombus",
      "color": "#13C2C2",
      "label": "ChoiceGoodsState",
      "stateId": "ChoiceGoodsState",
      "stateType": "Choice",
      "x": 221.60527099609374,
      "y": 126.47110877990724,
      "id": "882b4bcc",
      "stateProps": {},
      "index": 8
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-rect",
      "color": "#1890FF",
      "label": "ReduceMoney",
      "stateId": "ReduceMoney",
      "stateType": "ServiceTask",
      "stateProps": {
        "CompensateState": "ReduceMoneyCompensation",
        "Type": "ServiceTask",
        "ServiceName": "doCreateOrderOperation",
        "ServiceMethod": "reduceMoney",
        "Next": "ReduceMoneyState",
        "Input": [
          "$.[businessKey]",
          "$.[userId]"
        ],
        "Output": {
          "ReduceMoneyResult": "$.#root"
        },
        "Status": {
          "#root==true": "SU",
          "#root == false": "FA",
          "$Exception{java.lang.Throwable}": "UN"
        }
      },
      "x": 222.10527099609374,
      "y": 238.47110877990724,
      "id": "e642a93e",
      "index": 9
    },
    {
      "type": "node",
      "size": "80*72",
      "shape": "flow-rhombus",
      "color": "#13C2C2",
      "label": "ReduceMoneyState",
      "stateId": "ReduceMoneyState",
      "stateType": "Choice",
      "x": 220.60527099609374,
      "y": 361.47110877990724,
      "id": "bb6b3f2e",
      "stateProps": {},
      "index": 10
    },
    {
      "type": "node",
      "size": "72*72",
      "shape": "flow-circle",
      "color": "#05A465",
      "label": "Succeed",
      "stateId": "Succeed",
      "stateType": "Succeed",
      "x": 220.60527099609374,
      "y": 851.3333358764648,
      "id": "d4e7e04e",
      "stateProps": {
        "Type": "Succeed"
      },
      "index": 11
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-rect",
      "color": "#1890FF",
      "label": "CreateOrder",
      "stateId": "CreateOrder",
      "stateType": "ServiceTask",
      "stateProps": {
        "CompensateState": "CreateOrderCompensation",
        "Type": "ServiceTask",
        "ServiceName": "createOrderService",
        "ServiceMethod": "createOrder",
        "Next": "CreateOrderState",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[goodsId]",
          "$.[goodsCount]"
        ],
        "Output": {
          "CreateOrderResult": "$.#root"
        },
        "Status": {
          "#root==true": "SU",
          "#root == false": "FA",
          "$Exception{java.lang.Throwable}": "UN"
        }
      },
      "x": 220.60527099609374,
      "y": 537.9711087799072,
      "id": "101b97df",
      "index": 13
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "#722ED1",
      "label": "ReduceGoods补偿",
      "stateId": "ReduceGoodsCompensation",
      "stateType": "Compensation",
      "stateProps": {
        "ServiceName": "doCreateOrderOperation",
        "ServiceMethod": "reduceGoodsCompensation",
        "Input": [
          "$.[businessKey]",
          "$.[goodsId]"
        ]
      },
      "x": -43.66667175292969,
      "y": -4.52889122009276,
      "id": "670994f3"
    },
    {
      "type": "node",
      "size": "39*39",
      "shape": "flow-circle",
      "color": "red",
      "label": "ReduceGoodsCatch",
      "stateId": "ReduceGoodsCatch",
      "stateType": "Catch",
      "x": 278.60527099609374,
      "y": -4.52889122009276,
      "id": "0a75001d"
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "red",
      "label": "ReduceGoodsCompensationTrigger",
      "stateId": "ReduceGoodsCompensationTrigger",
      "stateType": "CompensationTrigger",
      "x": 489.3333282470703,
      "y": -6.333335876464844,
      "id": "ea548fae"
    },
    {
      "type": "node",
      "size": "72*72",
      "shape": "flow-circle",
      "color": "red",
      "label": "Fail",
      "stateId": "Fail",
      "stateType": "Fail",
      "stateProps": {
        "ErrorCode": "666",
        "Message": "全局业务失败"
      },
      "x": 702.3333282470703,
      "y": 138.66666412353516,
      "id": "d6d40f5c"
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "#722ED1",
      "label": "ReduceMoneyCompensation",
      "stateId": "ReduceMoneyCompensation",
      "stateType": "Compensation",
      "stateProps": {
        "ServiceName": "doCreateOrderOperation",
        "ServiceMethod": "reduceMoneyCompensation",
        "Input": [
          "$.[businessKey]",
          "$.[userId]"
        ]
      },
      "x": -42.66667175292969,
      "y": 238.97110877990724,
      "id": "e0bdb122"
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "red",
      "label": "ReduceMoneyCompensationTrigger",
      "stateId": "ReduceMoneyCompensationTrigger",
      "stateType": "CompensationTrigger",
      "x": 490.1666564941406,
      "y": 239.47110877990724,
      "id": "f95c5ba4"
    },
    {
      "type": "node",
      "size": "39*39",
      "shape": "flow-circle",
      "color": "red",
      "label": "ReduceMoneyCatch",
      "stateId": "ReduceMoneyCatch",
      "stateType": "Catch",
      "x": 277.60527099609374,
      "y": 238.97110877990724,
      "id": "5b0ed40b"
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "#722ED1",
      "label": "CreateOrderCompensation",
      "stateId": "CreateOrderCompensation",
      "stateType": "Compensation",
      "stateProps": {
        "ServiceName": "createOrderService",
        "ServiceMethod": "createOrderCompensation",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[goodsId]",
          "$.[goodsCount]"
        ]
      },
      "x": -50.333343505859375,
      "y": 538.4711087799072,
      "id": "99eda994"
    },
    {
      "type": "node",
      "size": "39*39",
      "shape": "flow-circle",
      "color": "red",
      "label": "CreateOrderCatch",
      "stateId": "CreateOrderCatch",
      "stateType": "Catch",
      "x": 276.10527099609374,
      "y": 536.6666641235352,
      "id": "bf4f0b7e"
    },
    {
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "red",
      "label": "CreateOrderCompensationTrigger",
      "stateId": "CreateOrderCompensationTrigger",
      "stateType": "CompensationTrigger",
      "x": 521.6666564941406,
      "y": 538.9711087799072,
      "id": "28bf46d3"
    },
    {
      "type": "node",
      "size": "80*72",
      "shape": "flow-rhombus",
      "color": "#13C2C2",
      "label": "CreateOrderState",
      "stateId": "CreateOrderState",
      "stateType": "Choice",
      "x": 220.60527099609374,
      "y": 675.6666641235352,
      "id": "35113d56",
      "stateProps": {},
      "index": 12
    }
  ],
  "edges": [
    {
      "source": "db4c4a01",
      "sourceAnchor": 2,
      "target": "ed9b4961",
      "targetAnchor": 0,
      "id": "56512448",
      "shape": "flow-polyline-round",
      "index": 0
    },
    {
      "source": "ed9b4961",
      "sourceAnchor": 2,
      "target": "882b4bcc",
      "targetAnchor": 0,
      "id": "02dd82f0",
      "shape": "flow-polyline-round",
      "index": 1
    },
    {
      "source": "882b4bcc",
      "sourceAnchor": 2,
      "target": "e642a93e",
      "targetAnchor": 0,
      "id": "8a20e337",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Expression": "[ReduceGoodsResult]==true",
        "Next": "ReduceMoney"
      },
      "index": 2,
      "label": ""
    },
    {
      "source": "e642a93e",
      "sourceAnchor": 2,
      "target": "bb6b3f2e",
      "targetAnchor": 0,
      "id": "d80e333a",
      "shape": "flow-polyline-round",
      "index": 3
    },
    {
      "source": "bb6b3f2e",
      "sourceAnchor": 2,
      "target": "101b97df",
      "targetAnchor": 0,
      "id": "c8f07d89",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Expression": "[ReduceMoneyResult]==true",
        "Next": "CreateOrder"
      },
      "index": 4,
      "label": ""
    },
    {
      "source": "ed9b4961",
      "sourceAnchor": 3,
      "target": "670994f3",
      "targetAnchor": 1,
      "id": "5c40049a",
      "shape": "flow-polyline-round",
      "style": {
        "lineDash": "4",
        "endArrow": false
      },
      "type": "Compensation"
    },
    {
      "source": "0a75001d",
      "sourceAnchor": 1,
      "target": "ea548fae",
      "targetAnchor": 3,
      "id": "7f5fff3e",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Exceptions": [
          "java.lang.Throwable"
        ],
        "Next": "ReduceGoodsCompensationTrigger"
      },
      "label": ""
    },
    {
      "source": "ea548fae",
      "sourceAnchor": 1,
      "target": "d6d40f5c",
      "targetAnchor": 0,
      "id": "9a6fd7e4",
      "shape": "flow-polyline-round"
    },
    {
      "source": "882b4bcc",
      "sourceAnchor": 1,
      "target": "ea548fae",
      "targetAnchor": 2,
      "id": "ff64d724",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Expression": "[ReduceGoodsResult]==false",
        "Next": "ReduceGoodsCompensationTrigger"
      },
      "label": ""
    },
    {
      "source": "e642a93e",
      "sourceAnchor": 3,
      "target": "e0bdb122",
      "targetAnchor": 1,
      "id": "1be846e4",
      "shape": "flow-polyline-round",
      "style": {
        "lineDash": "4",
        "endArrow": false
      },
      "type": "Compensation"
    },
    {
      "source": "5b0ed40b",
      "sourceAnchor": 1,
      "target": "f95c5ba4",
      "targetAnchor": 3,
      "id": "654b410d",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Exceptions": [
          "java.lang.Throwable"
        ],
        "Next": "ReduceMoneyCompensationTrigger"
      },
      "label": ""
    },
    {
      "source": "f95c5ba4",
      "sourceAnchor": 1,
      "target": "d6d40f5c",
      "targetAnchor": 2,
      "id": "ddb6958e",
      "shape": "flow-polyline-round"
    },
    {
      "source": "bb6b3f2e",
      "sourceAnchor": 1,
      "target": "f95c5ba4",
      "targetAnchor": 2,
      "id": "b57c61be",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Expression": "[ReduceMoneyResult]==false",
        "Next": "ReduceMoneyCompensationTrigger"
      },
      "label": ""
    },
    {
      "source": "101b97df",
      "sourceAnchor": 3,
      "target": "99eda994",
      "targetAnchor": 1,
      "id": "26d69c8e",
      "shape": "flow-polyline-round",
      "style": {
        "lineDash": "4",
        "endArrow": false
      },
      "type": "Compensation"
    },
    {
      "source": "bf4f0b7e",
      "sourceAnchor": 1,
      "target": "28bf46d3",
      "targetAnchor": 3,
      "id": "8983c877",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Exceptions": [
          "java.lang.Throwable"
        ],
        "Next": "CreateOrderCompensationTrigger"
      },
      "label": ""
    },
    {
      "source": "28bf46d3",
      "sourceAnchor": 1,
      "target": "d6d40f5c",
      "targetAnchor": 2,
      "id": "d781fbc9",
      "shape": "flow-polyline-round"
    },
    {
      "source": "101b97df",
      "sourceAnchor": 2,
      "target": "35113d56",
      "targetAnchor": 0,
      "id": "2e639684",
      "shape": "flow-polyline-round"
    },
    {
      "source": "35113d56",
      "sourceAnchor": 2,
      "target": "d4e7e04e",
      "targetAnchor": 0,
      "id": "b9bf4a83",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Expression": "[CreateOrderResult]==true",
        "Next": "Succeed"
      },
      "label": ""
    },
    {
      "source": "35113d56",
      "sourceAnchor": 1,
      "target": "28bf46d3",
      "targetAnchor": 2,
      "id": "541f04ee",
      "shape": "flow-polyline-round",
      "stateProps": {
        "Expression": "[CreateOrderResult]==false",
        "Next": "CreateOrderCompensationTrigger"
      },
      "label": ""
    }
  ]
}

json解释:

首先start节点中的Name是你java代码中调用状态机的标识,Next标识下一个运行的节点是ReduceGoods

ReduceGoods节点中,是个扣减库存的服务,所以Type是ServiceTask、
这里的ServiceName是你注册到spring容器中的服务的bean的名字(下面我会给代码全图给你们参考),ServiceMethods是上文那个bean中的方法reduceGoodsCount,Next是下个节点应该执行的服务,input是reduceGoodsCount方法的入参,这个businessKey是必要的,代码中调用startWithBusinessKey时需要,output是服务的输出ReduceGoodsResult这个节点需要和下面的判断节点一致,status中,我的服务返回的是true和false,根据这个判断这个ServiceTask节点是否执行成功, “$Exception{java.lang.Throwable}”: "UN"代表服务报错,CompensateState代表是ReduceGoods的补偿方法,在服务失败或报错后的方法,一般是补偿操作,举个例子就是比如这个服务扣库存了,补偿就是加回去
在这里插入图片描述

然后看到流程图中的左侧紫色模块,这些是服务的补偿服务,json中他的Type是Compensation。代表补偿,输入和非补偿服务是一样的,这个实际业务场景中根据业务而变,未必是一样的
在这里插入图片描述

ReduceGoods执行完毕后,来到ChoiceGoodsState,在这里我的判断定义在线条中在这里插入图片描述

Catch:
在这里插入图片描述

补偿触发器:
在这里插入图片描述
而选择节点我是空的:
在这里插入图片描述

ReduceGoods执行成功后的判断:
在这里插入图片描述

ps:
此json的Choice和成功或失败的线条遇到一个坑:如果有Choice的两个分支中其中一个的Props写的没对应,java代码中调用会报:No choice matched, maybe it is a bug. Choice state name: CreateOrderState

其他节点基本和上述一致,对应的bean名和方法,入参,出参,补偿方法,判断条件相应改变即可

java代码中:

将刚才的json存到你的TM所属服务的resources中:
在这里插入图片描述

接下来要配config:

package com.example.createorder.config;


import io.seata.saga.engine.config.DbStateMachineConfig;
import io.seata.saga.engine.impl.ProcessCtrlStateMachineEngine;
import io.seata.saga.rm.StateMachineEngineHolder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@Component
public class StateMachineConfiguration {

    
    @Bean
    public ThreadPoolExecutorFactoryBean threadExecutor(){
        ThreadPoolExecutorFactoryBean threadExecutor = new ThreadPoolExecutorFactoryBean();
        threadExecutor.setThreadNamePrefix("SAGA_ASYNC_EXE_");
        threadExecutor.setCorePoolSize(1);
        threadExecutor.setMaxPoolSize(20);
        return threadExecutor;
    }

    @Bean
    public DbStateMachineConfig dbStateMachineConfig(ThreadPoolExecutorFactoryBean threadExecutor, DataSource hikariDataSource) throws IOException {
        DbStateMachineConfig dbStateMachineConfig = new DbStateMachineConfig();
        dbStateMachineConfig.setDataSource(hikariDataSource);
        dbStateMachineConfig.setThreadPoolExecutor((ThreadPoolExecutor) threadExecutor.getObject());

//        这里的setResources如果你用的seata是1.4.1以上版本,这里的入参应该是String[],我试了几种方法都加载不进去,然后把seata降到了1.4.1
        dbStateMachineConfig.setResources(new PathMatchingResourcePatternResolver().getResources("classpath*:statelang/*.json"));//json文件
        dbStateMachineConfig.setEnableAsync(true);
        dbStateMachineConfig.setApplicationId("myfirstsaga");
        dbStateMachineConfig.setTxServiceGroup("default_tx_group");//这个和我上一篇讲的一致,要和你涉及的子事务一致

        return dbStateMachineConfig;
    }

    @Bean
    public ProcessCtrlStateMachineEngine stateMachineEngine(DbStateMachineConfig dbStateMachineConfig){
        ProcessCtrlStateMachineEngine stateMachineEngine = new ProcessCtrlStateMachineEngine();
        stateMachineEngine.setStateMachineConfig(dbStateMachineConfig);
        return stateMachineEngine;
    }

    @Bean
    public StateMachineEngineHolder stateMachineEngineHolder(ProcessCtrlStateMachineEngine stateMachineEngine){
        StateMachineEngineHolder stateMachineEngineHolder = new StateMachineEngineHolder();
        stateMachineEngineHolder.setStateMachineEngine(stateMachineEngine);
        return stateMachineEngineHolder;
    }

}

yml配置:



server:
  port: 3333
spring:
  application:
    name: createOrder
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        ip: 127.0.0.1
        register-enabled: true

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/orders?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true




dubbo:
  application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
    name: Consumer-createOrder
  registry: #注册中心配置,用于配置连接注册中心相关信息。
    address: nacos://127.0.0.1:8848
  protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
    name: dubbo
    port: 20880
  scan:
    base-packages: com.example.reducegoods.service  #服务暴露与发现消费所在的package





#
#seata:
##  enable-auto-data-source-proxy: true
#  enabled: true
#  tx-service-group: local_saga
#
#  service:
#    grouplist:
#      seata-server: 127.0.0.1:8091
#    vgroupMapping:
#      local_saga: default
##
seata:

  enabled: true
  tx-service-group: default_tx_group


  service:
    default:
      grouplist:
        seata-server: 127.0.0.1:8091
    vgroupMapping:
      default_tx_group: default





#  saga:
#    enabled: true
#    state-machine:
#      table-prefix: seata_
#      enable-async: false
#      async-thread-pool:
#        core-pool-size: 1
#        max-pool-size: 20
#        keep-alive-time: 60
#      trans-operation-timeout: 1800000
#      service-invoke-timeout: 300000
#      auto-register-resources: true
#      resources:
#        - classpath*:statelang/saga.json
#      default-tenant-id: 000001
#      charset: UTF-8

另外的扣库存和扣余额的配置文件:
减余额:


server:
  port: 2222
spring:
  application:
    name: reduceMoney
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        ip: 127.0.0.1
        register-enabled: true

#    alibaba:
#      seata:
#        tx-service-group: default_tx_group

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/users?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true




dubbo:
  application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
    name: reduceMoney
  registry: #注册中心配置,用于配置连接注册中心相关信息。
    address: nacos://127.0.0.1:8848
  protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
    name: dubbo
    port: 20881
  scan:
    base-packages: com.example.reducemoney.service  #服务暴露与发现消费所在的package








seata:
  enable-auto-data-source-proxy: true
  enabled: true
  tx-service-group: default_tx_group
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
  registry:
    type: nacos
    nacos:
        application: seata-server
        server-addr: 127.0.0.1:8848
        group: SEATA_GROUP
        namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
        username: ""
        password: ""
  service:
    # 事务组对应的集群民称
    vgroupMapping:
      default_tx_group: default

减库存:

server:
  port: 1111
spring:
  application:
    name: reduceGoods
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        ip: 127.0.0.1
#        register-enabled: true

#    alibaba:
#      seata:
#        tx-service-group: default_tx_group
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/goods?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

mybatis:
  mapper-locations: classpath:mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true



dubbo:
  application: #应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
    name: reduceGoods
  registry: #注册中心配置,用于配置连接注册中心相关信息。
    address: nacos://127.0.0.1:8848
  protocol: #协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
    name: dubbo
    port: 20880
  scan:
    base-packages: com.example.reducegoods.service  #服务暴露与发现消费所在的package




seata:
  enable-auto-data-source-proxy: true
  enabled: true
  tx-service-group: default_tx_group
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: d23703ee-09aa-444b-83b5-1c7f8ca7a4a7
      username: ""
      password: ""
  service:
    # 事务组对应的集群民称
    vgroupMapping:
      default_tx_group: default


库存服务代码:

package com.example.reducegoods.serviceImpl;


import com.example.reducegoods.dao.ReduceGoodsDao;
import com.example.reducegoods.service.ReduceGoodsService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Service
@Component
public class ReduceGoodsServiceImpl  implements ReduceGoodsService {
    @Autowired
    ReduceGoodsDao reduceGoodsDao;


    @Override
    public int reduceGoodsCount(String id) {
        System.out.println("减库存id:"+id);
        int result = 0;
        try {
            result = reduceGoodsDao.reduceGoodsCount(id);
        }
        catch (Exception e){
            e.printStackTrace();
            throw e;
        }
        return result;

    }
}

代码结构:
在这里插入图片描述
余额服务同理,注意这个@service是dubbo的

接下来是用创建订单服务调用这两个服务:
创建订单服务Controller:

@RestController
public class MakeOrderController {


    @Autowired
    CreateOrderService createOrderService;

    @Autowired
    StateMachineEngine stateMachineEngine;



    @RequestMapping("/createOrder/{userId}/{goodsId}/{goodsCount}")
    public void createOrder(@PathVariable("userId")String userId,@PathVariable("goodsId")String goodsId,@PathVariable("goodsCount")String goodsCount){
        Map<String, Object> startParams = new HashMap<>(4);
        //唯一健
        String businessKey = String.valueOf(System.currentTimeMillis());
        startParams.put("businessKey", businessKey);
        startParams.put("userId", userId);
        startParams.put("goodsId", goodsId);
        startParams.put("goodsCount", goodsCount);


        StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("startCreateOrder", null,businessKey,startParams);

        if(ExecutionStatus.SU.equals(inst.getStatus())){
            System.out.println("成功"+inst.getId());
        }else{
            System.out.println("失败"+inst.getId());
        }





    }

}

服务层接口:

package com.example.createorder.service;

public interface DoCreateOrderOperation {

    boolean reduceGoodsCount(String businessKey,String goodsId);

    boolean reduceGoodsCompensation();


    boolean reduceMoney(String businessKey,String userId);

    boolean reduceMoneyCompensation();



}

package com.example.createorder.service;

public interface CreateOrderService {
    boolean createOrder(String orderId,String userId,String goodsId,String goodsCount);

    boolean createOrderCompensation();
}

在这里插入图片描述

DoCreateOrderOpeartionImpl:

package com.example.createorder.serviceImpl;


import com.example.reducegoods.service.ReduceGoodsService;
import com.example.reducemoney.service.ReduceMoneyService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;


@Service("doCreateOrderOperation")
public class DoCreateOrderOperationImpl implements com.example.createorder.service.DoCreateOrderOperation {

    @DubboReference
    ReduceGoodsService reduceGoodsService;

    @DubboReference
    ReduceMoneyService reduceMoneyService;

    //减库存
    @Override
    public boolean reduceGoodsCount(String businessKey,String goodsId) {

//        System.out.println("http调用扣库存,接受到的businessKey为:"+businessKey);
//        System.out.println("http调用扣库存,接受到的goodsid为:"+goodsId);
//        resttemplate是http形式调用服务
//        String reduceGoodsUrl="http://"+"reduceGoods"+"/reduceGoods/"+goodsId;
//        String forObject = restTemplate.getForObject(reduceGoodsUrl, String.class);

//        dubbo rpc调用
        System.out.println("dubbp rpc调用扣库存,接受到的businessKey为:"+businessKey);
        System.out.println("dubbp rpc调用扣库存,接受到的goodsid为:"+goodsId);
        int i = reduceGoodsService.reduceGoodsCount(goodsId);


        if (i==1){
            return true;
        }
        else {
            return false;
        }
    }

    @Override
    public boolean reduceGoodsCompensation() {
        System.out.println("执行扣减库存补偿操作");
        return true;
    }

    @Override
    public boolean reduceMoney(String businessKey, String userId) {

//        System.out.println("http调用扣钱,接受到的businessKey为:"+businessKey);
//        System.out.println("http调用扣钱,接受到的userId为:"+userId);
//        restttemplate是http调用方式
//        String reduceMoneyUrl="http://"+"reduceMoney"+"/reduceMoney/"+userId;
//        String forObject = restTemplate.getForObject(reduceMoneyUrl, String.class);

//        dubbo rpc调用
        System.out.println("dubbp rpc调用扣钱,接受到的businessKey为:"+businessKey);
        System.out.println("dubbp rpc调用扣钱,接受到的userId为:"+userId);
        int i = reduceMoneyService.reduceRestMoney(userId);

        if (i==1){
            return true;
        }
        else {
            return false;
        }

    }

    @Override
    public boolean reduceMoneyCompensation() {
        System.out.println("调用扣减余额的补偿操作");
        return true;
    }


}

因为在json 中定义的扣库存和扣余额服务是注册在外部的其他spring容器中的,所以是在是当前容器中调用其他模块的服务实现,相当于套了一层
补偿方法这里没做处理,就打印个东西示意

接下来启动三个服务,前提是将seata和nacos起起来,我这里注册中心用的naocs,调用用dubbo rpc
这个CreateOrder服务启动成功后,可以在数据库seata_state_machine_def表中看到注册成功的注册机数据:
在这里插入图片描述
调用流程:
扣库存->扣余额->创建订单

一、模拟成功情况:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一、模拟第三步创建订单失败情况:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,第三步以及之前的步骤都执行了补偿操作,如果你在第二步报错,那就会执行1,2步的补偿操作

如果过程中出现safe guard client , should not be called ,must have a bug,把dubbo版本升到2.7.12

    <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.12</version>
        </dependency>

参考:
参考1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SeataSAGA模式是一种分布式事务处理模式,它采用了Saga Pattern来管理和协调分布式事务。 在SAGA模式中,一个分布式事务被拆分为多个局部事务,每个局部事务负责执行一部分操作,并在需要时发送补偿操作以回滚已执行的操作。 Seata作为分布式事务协调器,负责协调和管理SAGA模式下的分布式事务。当一个事务发起请求时,Seata会将其视为一个全局事务,并为每个局部事务创建一个参与者(RM,Resource Manager)。 在SAGA模式中,每个局部事务都有两个阶段:正向阶段(Forward Phase)和补偿阶段(Compensation Phase)。正向阶段负责执行实际的业务逻辑操作,而补偿阶段负责回滚已执行的操作。 当一个局部事务的正向阶段完成后,Seata会记录该操作的执行状态,并等待下一步指令。如果后续的操作成功执行,Seata会继续执行下一个局部事务的正向阶段;如果有任何一个操作失败,Seata会根据之前记录的执行状态,按照相反的顺序执行补偿阶段,回滚已执行的操作。 通过这种方式,Seata能够实现分布式事务的管理和协调,保证事务的一致性和可靠性。SAGA模式相比于传统的两阶段提交(2PC)协议,具有更好的可扩展性和容错性,因为它将事务拆分为多个局部事务,并通过补偿阶段来实现回滚。 需要注意的是,SeataSAGA模式是基于TCC(Try-Confirm-Cancel)模式实现的,因此每个局部事务也包含了Try、Confirm和Cancel三个阶段的操作。 总结起来,SeataSAGA模式采用了Saga Pattern来管理和协调分布式事务,通过拆分事务为多个局部事务和补偿阶段来实现事务的一致性和可靠性。每个局部事务都包含正向阶段和补偿阶段,通过记录状态和执行补偿操作来保证事务的正确执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值