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

news2024/12/24 11:30:04

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【大数据】Hadoop-HA-Federation-3.3.1集群高可用联邦安装部署文档(建议收藏哦)

背景概述 单 NameNode 的架构使得 HDFS 在集群扩展性和性能上都有潜在的问题&#xff0c;当集群大到一定程度后&#xff0c;NameNode 进程使用的内存可能会达到上百 G&#xff0c;NameNode 成为了性能的瓶颈。因而提出了 namenode 水平扩展方案-- Federation。 Federation 中…

C语言---字符串函数总结

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;夏目的C语言宝藏 &#x1f4ac;总结&#xff1a;希望你看完之…

ChatGPT国内使用方法全攻略(完整图文教程)

你好呀&#xff0c;我是月亮&#xff0c;一个90后的老程序员啦~ 最近ChatGPT完全火出圈了。 相关教程很多&#xff0c;我整理了一份网盘汇总&#xff0c;包含注册、谷歌浏览器插件使用、国内面注册平台&#xff0c;需要的小伙伴自取~ 网盘地址&#xff1a;使用方式汇总文档 …

数据库实践LAB大纲 06 INDEX

索引 索引是一个列表 —— 若干列集合和这些值的记录在数据表存储位置的物理地址 作用 加快检索速度唯一性索引 —— 保障数据唯一性加速表的连接分组和排序进行检索的时候 —— 减少时间消耗 一般建立原则 经常查询的数据主键外键连接字段排序字段少涉及、重复值多的字段…

分享114个JS菜单导航,总有一款适合您

分享114个JS菜单导航&#xff0c;总有一款适合您 114个JS菜单导航下载链接&#xff1a;https://pan.baidu.com/s/1t4_v0PipMjw3ULTLqkEiDQ?pwdgoi2 提取码&#xff1a;goi2 Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj $.ajax({type: &quo…

“万字“ Java I/O流讲解

Java I/O流讲解 每博一文案 谁让你读了这么多书&#xff0c;又知道了双水村以外还有一个大世界&#xff0c;如果从小你就在这个天地里&#xff0c;日出而作&#xff0c;日落而息。 那你现在就会和众乡亲抱同一理想&#xff1a;经过几年的辛劳&#xff0c;像大哥一样娶个满意的…

2023年中国各大城市薪酬报告出炉

全国地区&#xff1a;https://download.csdn.net/download/std86021/87322224北京&#xff1a;https://download.csdn.net/download/std86021/87273488上海&#xff1a;https://download.csdn.net/download/std86021/87322226广州&#xff1a;https://download.csdn.net/downlo…

Linux之文本搜索命令

文本搜索命令学习目标能够知道文本搜索使用的命令1. grep命令的使用命令说明grep文本搜索grep命令效果图:2. grep命令选项的使用命令选项说明-i忽略大小写-n显示匹配行号-v显示不包含匹配文本的所有行-i命令选项效果图:-n命令选项效果图:-v命令选项效果图:3. grep命令结合正则表…

linux基本功系列之hostname实战

文章目录前言一. hostname命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示本机的主机名3.2 临时修改主机名3.3 显示短格式的主机名3.4 显示主机的ip地址四. 永久修改主机名4.1 centos6 修改主机名的方式4.2 centos7中修改主机名永久生效总结前言 大家好&#xff0c;又见面…

Java、JSP企业快信系统的设计与实现

技术&#xff1a;Java、JSP等摘要&#xff1a;计算机网络的出现到现在已经经历了翻天覆地的重大改变。因特网也从最早的供科学家交流心得的简单的文本浏览器发展成为了商务和信息的中心。到了今天&#xff0c;互联网已经成为了大量应用的首选平台&#xff0c;人们已经渐渐习惯了…

02- 天池工业蒸汽量项目实战 (项目二)

忽略警告: warnings.filterwarnings("ignore") import warnings warnings.filterwarnings("ignore") 读取文件格式: pd.read_csv(train_data_file, sep\t) # 注意sep 是 , , 还是\ttrain_data.info() # 查看是否存在空数据及数据类型train_data.desc…

线程池框架

这是之前有做的一个可以接受用户传入任意类型的任务函数和任意参数&#xff0c;并且能拿到任务对应返回值的一个线程池框架&#xff0c;可以链接成动态库&#xff0c;用在相关项目里面。一共实现了两版&#xff0c;都是支持fixed和cached模式的&#xff0c;半同步半异步的&…

全局向量的词嵌入(GloVe)

诸如词-词共现计数的全局语料库统计可以来解释跳元模型。 交叉熵损失可能不是衡量两种概率分布差异的好选择&#xff0c;特别是对于大型语料库。GloVe使用平方损失来拟合预先计算的全局语料库统计数据。 对于GloVe中的任意词&#xff0c;中心词向量和上下文词向量在数学上是等…

分享113个JS菜单导航,总有一款适合您

分享113个JS菜单导航&#xff0c;总有一款适合您 113个JS菜单导航下载链接&#xff1a;https://pan.baidu.com/s/1d4nnh-UAxNnSp9kfMBmPAw?pwdcw23 提取码&#xff1a;cw23 Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj base_url "http…

MySQL 4:MySQL函数

为了提高代码的复用性和隐藏实现细节&#xff0c;MySQL提供了很多函数。函数可以理解为别人封装好的模板代码。 在MySQL中&#xff0c;函数有很多&#xff0c;主要可以分为以下几类&#xff1a;聚合函数、数学函数、字符串函数、日期函数、控制流函数、窗口函数。 一、聚合函…

研一寒假C++复习笔记--深拷贝和浅拷贝代码实例

目录 1--深拷贝和浅拷贝的基础概念 2--浅拷贝的代码实例 3--深拷贝代码实例 4--参考 1--深拷贝和浅拷贝的基础概念 ① 浅拷贝&#xff1a;简单的赋值拷贝操作&#xff1b; ② 深拷贝&#xff1a;在堆区重新申请空间&#xff0c;进行拷贝操作&#xff1b; 2--浅拷贝的代码…

CUDA中的统一内存

文章目录1. Unified Memory Introduction1.1. System Requirements1.2. Simplifying GPU Programming1.3. Data Migration and Coherency1.4. GPU Memory Oversubscription1.5. Multi-GPU1.6. System Allocator1.7. Hardware Coherency1.8. Access Counters2. Programming Mode…

格子玻尔兹曼法介绍

1 LBM简介格子玻尔兹曼法&#xff08;Lattice Boltzmann Method&#xff09;简称LBM&#xff0c;是一种CFD算法&#xff0c;可求解流动、传热等常见CFD问题。LBM基于格子玻尔兹曼方程&#xff08;LBE&#xff09;&#xff0c;从介观尺度&#xff08;mesoscope&#xff09;描述了…

Android Jetpack组件之WorkManager后台任务管理的介绍与使用(二)

一、介绍 通过上一篇文&#xff0c;Android Jetpack组件之WorkManager后台任务管理的介绍与使用(一)_蜗牛、Z的博客-CSDN博客 我们可以弄清楚workmanager从接入到使用的基本流程。基本可以满足我们日常。那只是简单的入门。如果遇到更复杂的功能&#xff0c;那简单的就无法满…

集中供热调度系统天然气仪表内网仪表图像识别案例

一、项目需求 出于能耗采集与冬季集中供暖工作的节能和能耗分析需要&#xff0c;要采集现场的6块天然气表计&#xff0c;并存储进入客户的mySQL数据库中&#xff0c;现场采集的表计不允许接线&#xff0c;且网络环境为内网环境&#xff0c;需要采集表计数据并存入数据库&#…