Seata Saga mode theory learning, production-level use example construction and precautions (1) | Spring Cloud57

I. Introduction

Through the following series of chapters:

docker-compose implements high-availability deployment of Seata Server | Spring Cloud 51

Seata AT mode theory study, transaction isolation and partial source code analysis | Spring Cloud 52

Spring Boot integrates Seata with AT mode distributed transaction example | Spring Cloud 53

Seata XA mode theory learning, use and precautions | Spring Cloud54

Seata TCC mode theory learning, production-level use example construction and precautions | Spring Cloud55

Solve the problems of idempotence, suspension and empty rollback in Seata TCC mode | Spring Cloud56

We have an in-depth understanding of the theory and use of its, , and transaction modes, and continue to learn the transaction modes of today ; Seataand ATto XAdistinguish it from the official website, we use it to build production-level examples to reduce the difficulty of getting started.TCCSeataSagaopenfeign

The theoretical part comes from Seatathe official website: http://seata.io/zh-cn/docs/user/saga.html

2. The overall mechanism

SagaThe mode is Seataa long-term transaction solution provided. In Sagathe mode, each participant in the business process submits a local transaction. When a certain participant fails, the previous successful participants are compensated. The first-stage forward service and the second-stage compensation Services are implemented by business development.

insert image description here

Theoretical basis: Hector & Kenneth published the paper Sagas (1987)

  • Applicable scene:

    • Long business process and many business processes
    • Actors contain services from other companies or legacy systems that cannot provide the three interfaces required by TCCthe pattern
  • Advantage:

    • Commit local transactions in one phase, lock-free, high performance
    • Event-driven architecture, participants can execute asynchronously, high throughput
    • Compensation services are easy to implement
  • shortcoming:

    • Isolation is not guaranteed

3. Implementation of Saga

The currently Seataprovided Sagamode is implemented based on the state machine engine, and the mechanism is:

  • Define the process of service invocation through the state diagram and generate jsonthe state language definition file
  • A node in the state diagram can be a service call, and the node can configure its compensation node
  • The state diagram is driven and executed jsonby the state machine engine. When an exception occurs, the state engine reversely executes the compensation node corresponding to the successful node and rolls back the transaction

    Note: Whether to compensate when an exception occurs can also be determined by the user

  • It can realize service orchestration requirements and support single selection, concurrency, sub-process, parameter conversion, parameter mapping, service execution status judgment, exception capture and other functions

3.1 State Machine Designer

Seata SagaA visual state machine designer is provided for user convenience:

http://seata.io/saga_designer/index.html#/

insert image description here

3.2 Best Practices

This example is carried out without installing the best practice, please create the transaction control table yourself, refer to: Solve the problems of idempotence, suspension, and empty rollback in Seata TCC mode | Spring Cloud56 learn from its ideas and practice by yourself.

3.2.1 Allow Null Compensation

  • Empty compensation: the original service is not executed, but the compensation service is executed
  • Cause:
    • The original service timed out (packet loss)
    • SagaTransaction triggers rollback
    • The original service request was not received, and the compensation request was received first

Therefore, it is necessary to allow empty compensation during service design, that is, when the business primary key to be compensated is not found, it returns compensation success and records the original business primary key.

3.2.2 Anti-suspension control

  • Suspension: the compensating service is executed before the original service
  • Cause:
    • The original service timed out (congestion)
    • SagaTransaction rollback, trigger rollback
    • Congested original service arrives

Therefore, it is necessary to check whether the current business primary key already exists in the business primary key recorded by the null compensation, and if it exists, the execution of the service must be refused.

3.2.3 Idempotent Control

  • Both the original service and the compensation service need to ensure idempotency. Since the network may time out, a retry strategy can be set. When retry occurs, idempotent control should be used to avoid repeated update of business data.

3.2.4 Lack of isolation responses

  • Since Sagathe transaction does not guarantee isolation, in extreme cases, the rollback operation may not be completed due to dirty writes. For example, in an extreme example, first recharge user A in a distributed transaction, and then deduct the balance from user B. The user recharges successfully. Before the transaction is submitted, user A consumes the balance. If the transaction is rolled back, there is no way to make compensation at this time. This is a typical problem caused by lack of isolation. The general solution in practice is:
    • The principle followed in the design of the business process “宁可长款, 不可短款”is that the long payment means that the customer has less money and the institution has more money. With the reputation of the institution, the customer can be refunded. On the contrary, it is a short payment, and the less money may not be recovered. Therefore, in the business process design, the payment must be deducted first.
    • Some business scenarios can allow the business to succeed in the end, and can continue to retry to complete the subsequent process if the rollback cannot be done. Therefore, the state machine engine needs to provide the ability to continue the execution of the context in addition to providing the business, so that the business can finally execute successfully and achieve final “回滚”能力consistency “向前”恢复. sexual purpose.

3.2.5 Performance Optimization

  • By configuring client parameters client.rm.report.success.enable=false, you can not report the branch status when the branch transaction is executed successfully server, thereby improving performance.

    When the status of the previous branch transaction has not been reported, the next branch transaction has been registered, and it can be considered that the previous branch transaction has actually succeeded

4. Examples

Based on Seata Sagaa pattern, demonstrates the commit and rollback of a distributed transaction.

This example is a case of placing an order for a commodity. There are three services and one public module:

  • order-saga: Business service, the user's order operation will be completed here.
  • account-saga: Account service, you can query/modify the user's account information
  • storage-saga: Warehousing service, you can query/modify the inventory quantity of the product.
  • common-tcc: Public module, including: entity class, openfeign interface, unified exception handling, etc.

An example state diagram is as follows:

insert image description here

3There are transaction participants in a distributed transaction Saga, which are: AccountService, StorageService, OrderService, where:

  • AccountService, StorageServicefor local Bean(calling remote httpservice), there is a reducemethod, which means balance deduction or inventory deduction, or, there is also a compensateReducemethod, which means compensation balance or inventory deduction
  • OrderServiceFor local Bean, there is a createOrdermethod that represents the creation of an order record, and a compensateOrdermethod that represents the compensation (removal) of an order record

4.1 State machine construction

4.1.1 State machine complete json

The corresponding complete state machine json:

{
    
    
  "nodes": [
    {
    
    
      "type": "node",
      "size": "80*72",
      "shape": "flow-rhombus",
      "color": "#13C2C2",
      "label": "AccountService-deduct-Choice",
      "stateId": "AccountService-deduct-Choice",
      "stateType": "Choice",
      "x": 467.875,
      "y": 286.5,
      "id": "c11238b3",
      "stateProps": {
    
    
        "Type": "Choice",
        "Choices": [
          {
    
    
            "Expression": "[deductResult] == true",
            "Next": "StorageService-deduct"
          }
        ],
        "Default": "Fail"
      },
      "index": 6
    },
    {
    
    
      "type": "node",
      "size": "39*39",
      "shape": "flow-circle",
      "color": "red",
      "label": "BService-save-catch",
      "stateId": "BService-save-catch",
      "stateType": "Catch",
      "x": 524.875,
      "y": 431.5,
      "id": "053ac3ac",
      "index": 7
    },
    {
    
    
      "type": "node",
      "size": "72*72",
      "shape": "flow-circle",
      "color": "#FA8C16",
      "label": "Start",
      "stateId": "Start",
      "stateType": "Start",
      "stateProps": {
    
    
        "StateMachine": {
    
    
          "Name": "order",
          "Comment": "经典的分布式调用",
          "Version": "0.0.1"
        },
        "Next": "AService"
      },
      "x": 467.875,
      "y": 53,
      "id": "973bd79e",
      "index": 11
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-rect",
      "color": "#1890FF",
      "label": "AccountService-deduct",
      "stateId": "AccountService-deduct",
      "stateType": "ServiceTask",
      "stateProps": {
    
    
        "Type": "ServiceTask",
        "ServiceName": "accountService",
        "Next": "AccountService-deduct-Choice",
        "ServiceMethod": "deduct",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[commodityCode]",
          "$.[count]"
        ],
        "Output": {
    
    
          "deductResult": "$.#root"
        },
        "Status": {
    
    
          "#root == true": "SU",
          "#root == false": "FA",
          "$Exception{java.lang.Throwable}": "UN"
        },
        "CompensateState": "AccountService-compensateDeduct",
        "Retry": []
      },
      "x": 467.875,
      "y": 172,
      "id": "e17372e4",
      "index": 12
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-rect",
      "color": "#1890FF",
      "label": "StorageService-deduct",
      "stateId": "StorageService-deduct",
      "stateType": "ServiceTask",
      "stateProps": {
    
    
        "Type": "ServiceTask",
        "ServiceName": "storageService",
        "ServiceMethod": "deduct",
        "CompensateState": "StorageService- compensateDeduct",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[commodityCode]",
          "$.[count]"
        ],
        "Output": {
    
    
          "deductResult": "$.#root"
        },
        "Status": {
    
    
          "#root == true": "SU",
          "#root == false": "FA",
          "$Exception{java.lang.Throwable}": "UN"
        },
        "Next": "StorageService-deduct-Choice"
      },
      "x": 467.125,
      "y": 411,
      "id": "a6c40952",
      "index": 13
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "#722ED1",
      "label": "AccountService-compensateDeduct",
      "stateId": "AccountService-compensateDeduct",
      "stateType": "Compensation",
      "stateProps": {
    
    
        "Type": "Compensation",
        "ServiceName": "accountService",
        "ServiceMethod": "compensateDeduct",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[commodityCode]",
          "$.[count]"
        ]
      },
      "x": 260.625,
      "y": 172.5,
      "id": "3b348652",
      "index": 14
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "#722ED1",
      "label": "StorageService-compensateDeduct",
      "stateId": "StorageService-compensateDeduct",
      "stateType": "Compensation",
      "stateProps": {
    
    
        "Type": "Compensation",
        "ServiceName": "storageService",
        "ServiceMethod": "compensateDeduct",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[commodityCode]",
          "$.[count]"
        ]
      },
      "x": 262.125,
      "y": 411,
      "id": "13b600b1",
      "index": 15
    },
    {
    
    
      "type": "node",
      "size": "72*72",
      "shape": "flow-circle",
      "color": "#05A465",
      "label": "Succeed",
      "stateId": "Succeed",
      "stateType": "Succeed",
      "x": 466.625,
      "y": 795,
      "id": "690e5c5e",
      "stateProps": {
    
    
        "Type": "Succeed"
      },
      "index": 16
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "red",
      "label": "Compensation\nTrigger",
      "stateId": "CompensationTrigger",
      "stateType": "CompensationTrigger",
      "x": 881.625,
      "y": 430.5,
      "id": "757e057f",
      "stateProps": {
    
    
        "Type": "CompensationTrigger",
        "Next": "Fail"
      },
      "index": 17
    },
    {
    
    
      "type": "node",
      "size": "72*72",
      "shape": "flow-circle",
      "color": "red",
      "label": "Fail",
      "stateId": "Fail",
      "stateType": "Fail",
      "stateProps": {
    
    
        "Type": "Fail",
        "ErrorCode": "FAILED",
        "Message": "buy failed"
      },
      "x": 881.125,
      "y": 285.5,
      "id": "0131fc0c",
      "index": 18
    },
    {
    
    
      "type": "node",
      "size": "39*39",
      "shape": "flow-circle",
      "color": "red",
      "label": "AccountService-deduct-catch",
      "stateId": "AccountService-deduct-catch",
      "stateType": "Catch",
      "x": 518.125,
      "y": 183,
      "id": "0955401d"
    },
    {
    
    
      "type": "node",
      "size": "80*72",
      "shape": "flow-rhombus",
      "color": "#13C2C2",
      "label": "StorageService-deduct-Choice",
      "stateId": "StorageService-deduct-Choice",
      "stateType": "Choice",
      "x": 466.875,
      "y": 545.5,
      "id": "27978f5d",
      "stateProps": {
    
    
        "Type": "Choice",
        "Choices": [
          {
    
    
            "Expression": "[deductResult] == true",
            "Next": "OrderService-createOrder"
          }
        ],
        "Default": "Fail"
      }
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-rect",
      "color": "#1890FF",
      "label": "OrderService-createOrder",
      "stateId": "OrderService-createOrder",
      "stateType": "ServiceTask",
      "stateProps": {
    
    
        "Type": "ServiceTask",
        "ServiceName": "orderService",
        "ServiceMethod": "createOrder",
        "CompensateState": "OrderService- compensateOrder",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[commodityCode]",
          "$.[count]"
        ],
        "Output": {
    
    
          "createOrderResult": "$.#root"
        },
        "Status": {
    
    
          "#root == true": "SU",
          "#root == false": "FA",
          "$Exception{java.lang.Throwable}": "UN"
        },
        "Next": "Succeed"
      },
      "x": 466.625,
      "y": 676,
      "id": "9351460d"
    },
    {
    
    
      "type": "node",
      "size": "110*48",
      "shape": "flow-capsule",
      "color": "#722ED1",
      "label": "OrderService-compensateOrder",
      "stateId": "OrderService-compensateOrder",
      "stateType": "Compensation",
      "stateProps": {
    
    
        "Type": "Compensation",
        "ServiceName": "orderService",
        "ServiceMethod": "compensateOrder",
        "Input": [
          "$.[businessKey]",
          "$.[userId]",
          "$.[commodityCode]",
          "$.[count]"
        ]
      },
      "x": 261.625,
      "y": 675.5,
      "id": "b2789952"
    },
    {
    
    
      "type": "node",
      "size": "39*39",
      "shape": "flow-circle",
      "color": "red",
      "label": "OrderService-createOrder-catch",
      "stateId": "OrderService-createOrder-catch",
      "stateType": "Catch",
      "x": 523.125,
      "y": 696,
      "id": "466cf242"
    }
  ],
  "edges": [
    {
    
    
      "source": "973bd79e",
      "sourceAnchor": 2,
      "target": "e17372e4",
      "targetAnchor": 0,
      "id": "f0a9008f",
      "index": 0
    },
    {
    
    
      "source": "e17372e4",
      "sourceAnchor": 2,
      "target": "c11238b3",
      "targetAnchor": 0,
      "id": "cd8c3104",
      "index": 2,
      "label": "执行结果",
      "shape": "flow-smooth"
    },
    {
    
    
      "source": "c11238b3",
      "sourceAnchor": 2,
      "target": "a6c40952",
      "targetAnchor": 0,
      "id": "e47e49bc",
      "stateProps": {
    
    },
      "label": "执行成功",
      "shape": "flow-smooth",
      "index": 3
    },
    {
    
    
      "source": "c11238b3",
      "sourceAnchor": 1,
      "target": "0131fc0c",
      "targetAnchor": 3,
      "id": "e3f9e775",
      "stateProps": {
    
    },
      "label": "执行失败",
      "shape": "flow-smooth",
      "index": 4
    },
    {
    
    
      "source": "053ac3ac",
      "sourceAnchor": 1,
      "target": "757e057f",
      "targetAnchor": 3,
      "id": "3f7fe6ad",
      "stateProps": {
    
    
        "Exceptions": [
          "java.lang.Throwable"
        ],
        "Next": "CompensationTrigger"
      },
      "label": "StorageService-deduct异常触发补偿",
      "shape": "flow-smooth",
      "index": 5
    },
    {
    
    
      "source": "e17372e4",
      "sourceAnchor": 3,
      "target": "3b348652",
      "targetAnchor": 1,
      "id": "52a2256e",
      "style": {
    
    
        "lineDash": "4"
      },
      "index": 8,
      "label": "",
      "shape": "flow-smooth"
    },
    {
    
    
      "source": "a6c40952",
      "sourceAnchor": 3,
      "target": "13b600b1",
      "targetAnchor": 1,
      "id": "474512d9",
      "style": {
    
    
        "lineDash": "4"
      },
      "index": 9
    },
    {
    
    
      "source": "757e057f",
      "sourceAnchor": 0,
      "target": "0131fc0c",
      "targetAnchor": 2,
      "id": "1abf48fa",
      "index": 10
    },
    {
    
    
      "source": "0955401d",
      "sourceAnchor": 1,
      "target": "757e057f",
      "targetAnchor": 1,
      "id": "654280aa",
      "shape": "flow-polyline-round",
      "stateProps": {
    
    
        "Exceptions": [
          "java.lang.Throwable"
        ],
        "Next": "CompensationTrigger"
      },
      "label": "AccountService-deduct异常触发补偿"
    },
    {
    
    
      "source": "a6c40952",
      "sourceAnchor": 2,
      "target": "27978f5d",
      "targetAnchor": 0,
      "id": "f25a12eb",
      "shape": "flow-polyline-round",
      "label": "执行结果"
    },
    {
    
    
      "source": "27978f5d",
      "sourceAnchor": 2,
      "target": "9351460d",
      "targetAnchor": 0,
      "id": "99d78285",
      "shape": "flow-smooth",
      "stateProps": {
    
    },
      "label": "执行成功"
    },
    {
    
    
      "source": "9351460d",
      "sourceAnchor": 2,
      "target": "690e5c5e",
      "targetAnchor": 0,
      "id": "82670a92",
      "shape": "flow-polyline-round"
    },
    {
    
    
      "source": "9351460d",
      "sourceAnchor": 3,
      "target": "b2789952",
      "targetAnchor": 1,
      "id": "5db6a545",
      "shape": "flow-polyline-round",
      "style": {
    
    
        "lineDash": "4",
        "endArrow": false
      },
      "type": "Compensation"
    },
    {
    
    
      "source": "466cf242",
      "sourceAnchor": 1,
      "target": "757e057f",
      "targetAnchor": 2,
      "id": "a9f55df2",
      "shape": "flow-polyline-round",
      "stateProps": {
    
    
        "Exceptions": [
          "java.lang.Throwable"
        ],
        "Next": "CompensationTrigger"
      },
      "label": "OrderService-createOrder异常触发补偿"
    },
    {
    
    
      "source": "27978f5d",
      "sourceAnchor": 1,
      "target": "0131fc0c",
      "targetAnchor": 1,
      "id": "c303cae6",
      "shape": "flow-polyline-round",
      "stateProps": {
    
    },
      "label": "执行失败"
    }
  ]
}

4.1.2 Start node Start

insert image description here
illustrate:

  • StateMachineThe next configuration is the "state machine" attribute
    • Name: Indicates the name of the state machine, which must be unique
    • Comment: A description of the state machine
    • Version: state machine definition version
  • Next: The "status" of the next execution after the service execution is completed (of the "status" node of the next execution ID)

4.1.3 Task node ServiceTask

insert image description here
Full Propscontent:

{
    
    
  "Type": "ServiceTask",
  "ServiceName": "accountService",
  "Next": "AccountService-deduct-Choice",
  "ServiceMethod": "deduct",
  "Input": [
    "$.[businessKey]",
    "$.[userId]",
    "$.[commodityCode]",
    "$.[count]"
  ],
  "Output": {
    
    
    "deductResult": "$.#root"
  },
  "Status": {
    
    
    "#root == true": "SU",
    "#root == false": "FA",
    "$Exception{java.lang.Throwable}": "UN"
  },
  "CompensateState": "AccountService-compensateDeduct",
  "Retry": []
}

illustrate:

  • ServiceName: service name, usually the service'sbeanId

  • ServiceMethod: service method name

  • CompensateState: Compensation "state" of the "state" (of the compensation "state" node ID)

  • Input: The input parameter list for invoking the service is an array, corresponding to the parameter list of the service method, $.indicating that the expression is used to get parameters from the state machine context, and the expression is used SpringEL. If it is a constant, just write the value directly.

  • Output: Assign the parameters returned by the service to the state machine context. It is a mapstructure, keywhich is put into the state machine context key(the state machine context is also one map), valueand the middle $.is an SpringELexpression, which means to get the value from the return parameter of the service. #rootRepresents the entire return parameter of the service

  • Status: Service execution state mapping. The framework defines three states, SUsuccess, FAfailure, and UNunknown. We need to map the state of service execution into these three states to help the framework judge the consistency of the entire transaction. It is a mapstructure and keya conditional expression , generally judge by taking the return value of the service or the exception thrown, the default is the SpringELexpression to judge the service return parameter, with $Exception{...}the beginning to indicate the judgment exception type. valueis to map the service execution status to this value when the conditional expression is true

  • Retry: The retry strategy after catching an exception is an array that can be configured with multiple rules, which is the Exceptionslist of matching exceptions, IntervalSecondsthe retry interval, MaxAttemptsthe maximum number of retries, BackoffRatethe multiple of the next retry interval relative to the previous retry interval, For example, if the last retry interval is 2seconds, BackoffRate=1.5the next retry interval is 3 seconds. ExceptionsThe attribute can be left unconfigured. If it is not configured, it means that the framework automatically matches the network timeout exception. When other exceptions occur during the retry process, the framework will re-match the rules and retry according to the new rules. The total number of retries for the same rule will not exceed the rule'sMaxAttempts

    "Retry": [
        {
          
          
            "Exceptions": ["io.seata.saga.engine.mock.DemoException"],
            "IntervalSeconds": 1.5,
            "MaxAttempts": 3,
            "BackoffRate": 1.5
        },
        {
          
          
            "IntervalSeconds": 1,
            "MaxAttempts": 3,
            "BackoffRate": 1.5
        }
    ]
    
  • Next: The "status" of the next execution after the service execution is completed (of the "status" node of the next execution ID)

  • IsForUpdate: Indicates that the service will update data, the default is false, if configured, CompensateStatethe default is true, the service with compensation service must be a data update service

  • IsPersist: Whether to store the execution log, the default is true, some query services can be configured to falsenot store the execution log to improve performance, because it can be executed repeatedly when the exception is recovered

  • IsAsync: Call the service asynchronously. Note: Because the asynchronous call to the service will ignore the return result of the service, the user-defined service execution status mapping (the following Statusattributes) will be ignored. The default is that the service call is successful. If the asynchronous call is submitted, it will fail (such as thread The pool is full), then the service execution status is failed

  • IsRetryPersistModeUpdate: When retrying forward, whether the log is updated based on the last failure log, the default is false, that is, a new retry log is added (priority is higher than that of the state machine attribute configuration)

  • IsCompensatePersistModeUpdate: When retrying backward compensation, whether the log is updated based on the last compensation log, the default is, that is, falsea new compensation log is added (priority is higher than that of the state machine attribute configuration)

  • Loop: Indicates whether the transaction node is a cyclic transaction, that is, the framework itself executes the transaction node cyclically by traversing the collection elements according to the configuration of the cyclic attribute. For specific usage, see: http://seata.io/zh-cn/docs/user/saga.html

When there is no configuration Statusto map the service execution status, the system will automatically judge the status:

  • If there is no exception, the execution is considered successful
  • If there is an exception, judge whether the exception is a network connection timeout, and if so, consider it to beFA
  • If it is other abnormality, IsForUpdate=truethe status of the service is UN, otherwise it isFA

How to judge the execution state of the entire state machine?
It is judged by the framework itself, and the state machine has two states: status(forward execution state), compensateStatus(compensation state)

  • If all services execute successfully (transaction commits successfully) thenstatus=SU, compensateStatus=null
  • If there is a service execution failure and there is an update service execution success without compensation (transaction commit failure) thenstatus=UN, compensateStatus=null
  • If there is a service execution failure and no update service execution is successful without compensation (transaction commit failure) thenstatus=FA, compensateStatus=null
  • If compensation succeeds (transaction rollback succeeds) thenstatus=FA/UN, compensateStatus=SU
  • Compensation occurs and there are services that are not compensated successfully (rollback failure) thenstatus=FA/UN, compensateStatus=UN
  • If there is a transaction commit or rollback failure, Seata Severit will continue to initiate retries

4.1.4 Select node Choice

insert image description here
Explanation:
ChoiceThe "state" of type is a single-choice route Choices:

  • Optional list of branches, only the first branch whose condition is met will be selected Expression:SpringEL表达式
  • Next: The "status" of the next execution after the service execution is completed (of the "status" node of the next execution ID)

Important: The following are choicethe default attributes of the line. If you do not modify the configuration, an exception will occur, and it is correct Props:{}.

insert image description here

4.1.5 Success node Succeed

insert image description here

illustrate:

  • Running to the "Succeed state" means that the state machine ends normally, and the normal end does not mean that it ends successfully. Whether it succeeds depends on whether each "state" is successful

4.1.6 Failed node Fail

insert image description here

illustrate:

  • Run until the " Failstatus" state machine ends abnormally, ErrorCodeand can be configured when it ends abnormally Message, indicating error codes and error messages, which can be used to return error codes and messages to the caller

4.1.7 Compensation Trigger Node CompensationTrigger

insert image description here

illustrate:

  • CompensationTriggerThe type stateis used to trigger compensation events and roll back distributed transactions
  • Next: The "status" of the next execution after the service execution is completed (of the "status" node of the next execution ID)

4.1.8 Compensation node

insert image description here

illustrate:

  • ServiceName: service name, usually the service'sbeanId
  • ServiceMethod: service method name
  • Input: The input parameter list for invoking the service is an array, corresponding to the parameter list of the service method, $.indicating that the expression is used to get parameters from the state machine context, and the expression is used SpringEL. If it is a constant, just write the value directly.

4.1.9 Exception catch node

insert image description here
insert image description here

illustrate:

  • Note 1The exception capture node should be overlaid on the graph of the state task node through the graph to realize the association between the two
  • Remarks 2By Catchconfiguring the attributes on the arrow line that the node radiates, specify what exception to capture, and after capturing the corresponding exception, specify the "state" of the next execution (the "state" node of the next execution ID)

4.1.10 How to trigger compensation

For increased flexibility. Users can control whether to roll back, because not all exceptions need to be rolled back, there may be some custom handling methods:

  • The compensation node cannot automatically trigger the compensation. For those that need to be compensated, they must be manually routed in the state machine jsonby Catchor attribute , or refer to the implementation method in the chapterChoicesCompensationTrigger4.1.9

4.1.11 Input definition of complex parameters

Example of complex parameters json:

{
    
    
    "Type": "ServiceTask",
    "ServiceName": "demoService",
    "ServiceMethod": "complexParameterMethod",
    "Next": "ChoiceState",
    "ParameterTypes" : ["java.lang.String", "int", "io.seata.saga.engine.mock.DemoService$People", "[Lio.seata.saga.engine.mock.DemoService$People;", "java.util.List", "java.util.Map"],
    "Input": [
        "$.[people].name",
        "$.[people].age",
        {
    
    
            "name": "$.[people].name",
            "age": "$.[people].age",
            "childrenArray": [
                {
    
    
                    "name": "$.[people].name",
                    "age": "$.[people].age"
                },
                {
    
    
                    "name": "$.[people].name",
                    "age": "$.[people].age"
                }
            ],
            "childrenList": [
                {
    
    
                    "name": "$.[people].name",
                    "age": "$.[people].age"
                },
                {
    
    
                    "name": "$.[people].name",
                    "age": "$.[people].age"
                }
            ],
            "childrenMap": {
    
    
                "lilei": {
    
    
                    "name": "$.[people].name",
                    "age": "$.[people].age"
                }
            }
        },
        [
            {
    
    
                "name": "$.[people].name",
                "age": "$.[people].age"
            },
            {
    
    
                "name": "$.[people].name",
                "age": "$.[people].age"
            }
        ],
        [
            {
    
    
                "@type": "io.seata.saga.engine.mock.DemoService$People",
                "name": "$.[people].name",
                "age": "$.[people].age"
            }
        ],
        {
    
    
            "lilei": {
    
    
                "@type": "io.seata.saga.engine.mock.DemoService$People",
                "name": "$.[people].name",
                "age": "$.[people].age"
            }
        }
    ],
    "Output": {
    
    
        "complexParameterMethodResult": "$.#root"
    }
}

The above complexParameterMethodmethod is defined as follows:

People complexParameterMethod(String name, int age, People people, People[] peopleArrya, List<People> peopleList, Map<String, People> peopleMap)

@Data
class People {
    
    
    private String name;
    private int age;
    private People[] childrenArray;
    private List<People> childrenList;
    private Map<String, People> childrenMap;
}

Pass in parameters when starting the state machine:

Map<String, Object> paramMap = new HashMap<>(1);
People people = new People();
people.setName("lilei");
people.setAge(18);
paramMap.put("people", people);
// 状态机名称
String stateMachineName = "demo";
StateMachineInstance inst = stateMachineEngine.start(stateMachineName, null, paramMap);

Note: ParameterTypesThe attribute can be passed without passing. The parameter list of the calling method has this kind of collection type that can have generics Map. ListBecause javathe compilation will lose the generics, this attribute needs to be used. At the same time, Inputadd jsonthis jsonattribute to the @typeTo declare generics (element types of collections)

4.1.12 Saga's json file hot deployment

Via: stateMachineEngine.getStateMachineConfig().getStateMachineRepository().registryByResources(). However, javacodes and services need to be implemented by themselves to support hot deployment.

4.1.13 Incomplete state machine instance recovery

Seata SagaThe client or Seata Serverserver that started the transaction is down or restarted, and the unfinished state machine instance can be restored by reading the recorded log of the local database. Seata ServerWill trigger transaction recovery.

4.2 Table structure required by Saga state machine

The address of the script GitHub: https://github.com/seata/seata/tree/1.6.1/script/client/saga/db

The table creation statement is as follows ( MySQLsyntax):

CREATE TABLE IF NOT EXISTS `seata_state_machine_def`
(
    `id`               VARCHAR(32)  NOT NULL COMMENT 'id',
    `name`             VARCHAR(128) NOT NULL COMMENT 'name',
    `tenant_id`        VARCHAR(32)  NOT NULL COMMENT 'tenant id',
    `app_name`         VARCHAR(32)  NOT NULL COMMENT 'application name',
    `type`             VARCHAR(20)  COMMENT 'state language type',
    `comment_`         VARCHAR(255) COMMENT 'comment',
    `ver`              VARCHAR(16)  NOT NULL COMMENT 'version',
    `gmt_create`       DATETIME(3)  NOT NULL COMMENT 'create time',
    `status`           VARCHAR(2)   NOT NULL COMMENT 'status(AC:active|IN:inactive)',
    `content`          TEXT COMMENT 'content',
    `recover_strategy` VARCHAR(16) COMMENT 'transaction recover strategy(compensate|retry)',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `seata_state_machine_inst`
(
    `id`                  VARCHAR(128)            NOT NULL COMMENT 'id',
    `machine_id`          VARCHAR(32)             NOT NULL COMMENT 'state machine definition id',
    `tenant_id`           VARCHAR(32)             NOT NULL COMMENT 'tenant id',
    `parent_id`           VARCHAR(128) COMMENT 'parent id',
    `gmt_started`         DATETIME(3)             NOT NULL COMMENT 'start time',
    `business_key`        VARCHAR(48) COMMENT 'business key',
    `start_params`        TEXT COMMENT 'start parameters',
    `gmt_end`             DATETIME(3) COMMENT 'end time',
    `excep`               BLOB COMMENT 'exception',
    `end_params`          TEXT COMMENT 'end parameters',
    `status`              VARCHAR(2) COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
    `compensation_status` VARCHAR(2) COMMENT 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
    `is_running`          TINYINT(1) COMMENT 'is running(0 no|1 yes)',
    `gmt_updated`         DATETIME(3) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `unikey_buz_tenant` (`business_key`, `tenant_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `seata_state_inst`
(
    `id`                       VARCHAR(48)  NOT NULL COMMENT 'id',
    `machine_inst_id`          VARCHAR(128) NOT NULL COMMENT 'state machine instance id',
    `name`                     VARCHAR(128) NOT NULL COMMENT 'state name',
    `type`                     VARCHAR(20)  COMMENT 'state type',
    `service_name`             VARCHAR(128) COMMENT 'service name',
    `service_method`           VARCHAR(128) COMMENT 'method name',
    `service_type`             VARCHAR(16) COMMENT 'service type',
    `business_key`             VARCHAR(48) COMMENT 'business key',
    `state_id_compensated_for` VARCHAR(50) COMMENT 'state compensated for',
    `state_id_retried_for`     VARCHAR(50) COMMENT 'state retried for',
    `gmt_started`              DATETIME(3)  NOT NULL COMMENT 'start time',
    `is_for_update`            TINYINT(1) COMMENT 'is service for update',
    `input_params`             TEXT COMMENT 'input parameters',
    `output_params`            TEXT COMMENT 'output parameters',
    `status`                   VARCHAR(2)   NOT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',
    `excep`                    BLOB COMMENT 'exception',
    `gmt_updated`              DATETIME(3) COMMENT 'update time',
    `gmt_end`                  DATETIME(3) COMMENT 'end time',
    PRIMARY KEY (`id`, `machine_inst_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

Due to space reasons, this chapter ends here first. For specific sample code construction, please refer to the next chapter.

Guess you like

Origin blog.csdn.net/ctwy291314/article/details/131321009