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
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 ; Seata
and AT
to XA
distinguish it from the official website, we use it to build production-level examples to reduce the difficulty of getting started.TCC
Seata
Saga
openfeign
The theoretical part comes from
Seata
the official website: http://seata.io/zh-cn/docs/user/saga.html
2. The overall mechanism
Saga
The mode is Seata
a long-term transaction solution provided. In Saga
the 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.
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
TCC
the 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 Seata
provided Saga
mode is implemented based on the state machine engine, and the mechanism is:
- Define the process of service invocation through the state diagram and generate
json
the 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
json
by 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 transactionNote: 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 Saga
A visual state machine designer is provided for user convenience:
http://seata.io/saga_designer/index.html#/
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)
Saga
Transaction 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)
Saga
Transaction 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
Saga
the 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.
- The principle followed in the design of the business process
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 successfullyserver
, 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 Saga
a 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 informationstorage-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:
3
There are transaction participants in a distributed transaction Saga
, which are: AccountService
, StorageService
, OrderService
, where:
AccountService
,StorageService
for localBean
(calling remotehttp
service), there is areduce
method, which means balance deduction or inventory deduction, or, there is also acompensateReduce
method, which means compensation balance or inventory deductionOrderService
For localBean
, there is acreateOrder
method that represents the creation of an order record, and acompensateOrder
method 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
illustrate:
StateMachine
The next configuration is the "state machine" attributeName
: Indicates the name of the state machine, which must be uniqueComment
: A description of the state machineVersion
: state machine definition version
Next
: The "status" of the next execution after the service execution is completed (of the "status" node of the next executionID
)
4.1.3 Task node ServiceTask
Full Props
content:
{
"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" nodeID
) -
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 usedSpringEL
. 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 amap
structure,key
which is put into the state machine contextkey
(the state machine context is also onemap
),value
and the middle$.
is anSpringEL
expression, which means to get the value from the return parameter of the service.#root
Represents the entire return parameter of the service -
Status
: Service execution state mapping. The framework defines three states,SU
success,FA
failure, andUN
unknown. 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 amap
structure andkey
a conditional expression , generally judge by taking the return value of the service or the exception thrown, the default is theSpringEL
expression to judge the service return parameter, with$Exception{...}
the beginning to indicate the judgment exception type.value
is 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 theExceptions
list of matching exceptions,IntervalSeconds
the retry interval,MaxAttempts
the maximum number of retries,BackoffRate
the multiple of the next retry interval relative to the previous retry interval, For example, if the last retry interval is2
seconds,BackoffRate=1.5
the next retry interval is 3 seconds.Exceptions
The 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 executionID
) -
IsForUpdate
: Indicates that the service will update data, the default isfalse
, if configured,CompensateState
the default istrue
, the service with compensation service must be a data update service -
IsPersist
: Whether to store the execution log, the default istrue
, some query services can be configured tofalse
not 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 followingStatus
attributes) 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,false
a 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 Status
to 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 be
FA
- If it is other abnormality,
IsForUpdate=true
the status of the service isUN
, 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) then
status=SU, compensateStatus=null
- If there is a service execution failure and there is an update service execution success without compensation (transaction commit failure) then
status=UN, compensateStatus=null
- If there is a service execution failure and no update service execution is successful without compensation (transaction commit failure) then
status=FA, compensateStatus=null
- If compensation succeeds (transaction rollback succeeds) then
status=FA/UN, compensateStatus=SU
- Compensation occurs and there are services that are not compensated successfully (rollback failure) then
status=FA/UN, compensateStatus=UN
- If there is a transaction commit or rollback failure,
Seata Sever
it will continue to initiate retries
4.1.4 Select node Choice
Explanation:
Choice
The "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 executionID
)
Important: The following are
choice
the default attributes of the line. If you do not modify the configuration, an exception will occur, and it is correctProps:{}
.
4.1.5 Success node Succeed
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
illustrate:
- Run until the "
Fail
status" state machine ends abnormally,ErrorCode
and can be configured when it ends abnormallyMessage
, 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
illustrate:
CompensationTrigger
The typestate
is used to trigger compensation events and roll back distributed transactionsNext
: The "status" of the next execution after the service execution is completed (of the "status" node of the next executionID
)
4.1.8 Compensation node
illustrate:
ServiceName
: service name, usually the service'sbeanId
ServiceMethod
: service method nameInput
: 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 usedSpringEL
. If it is a constant, just write the value directly.
4.1.9 Exception catch node
illustrate:
- Note
1
The 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
2
ByCatch
configuring 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 executionID
)
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
json
byCatch
or attribute , or refer to the implementation method in the chapterChoices
CompensationTrigger
4.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 complexParameterMethod
method 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:
ParameterTypes
The attribute can be passed without passing. The parameter list of the calling method has this kind of collection type that can have genericsMap
.List
Becausejava
the compilation will lose the generics, this attribute needs to be used. At the same time,Input
addjson
thisjson
attribute to the@type
To declare generics (element types of collections)
4.1.12 Saga's json file hot deployment
Via: stateMachineEngine.getStateMachineConfig().getStateMachineRepository().registryByResources()
. However, java
codes and services need to be implemented by themselves to support hot deployment.
4.1.13 Incomplete state machine instance recovery
Seata Saga
The client or Seata Server
server 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 Server
Will 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 ( MySQL
syntax):
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.