Seata Saga 模式理论学习、生产级使用示例搭建及注意事项(一) | Spring Cloud57

news2024/9/29 17:38:10

一、前言

通过以下系列章节:

docker-compose 实现Seata Server高可用部署 | Spring Cloud 51

Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52

Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53

Seata XA 模式理论学习、使用及注意事项 | Spring Cloud54

Seata TCC 模式理论学习、生产级使用示例搭建及注意事项 | Spring Cloud55

Seata TCC 模式下解决幂等、悬挂、空回滚问题 | Spring Cloud56

我们对Seata及其ATXATCC事务模式的理论、使用有了深入的了解,今天继续学习SeataSaga事务模式进行学习;并区别与官网,我们利用openfeign进行生产级示例搭建,降低入门难度。

理论部分来自Seata官网:http://seata.io/zh-cn/docs/user/saga.html

二、整体机制

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

在这里插入图片描述

理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)

  • 适用场景:

    • 业务流程长、业务流程多
    • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
  • 优势:

    • 一阶段提交本地事务,无锁,高性能
    • 事件驱动架构,参与者可异步执行,高吞吐
    • 补偿服务易于实现
  • 缺点:

    • 不保证隔离性

三、Saga的实现

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

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

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

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

3.1 状态机设计器

Seata Saga 提供了一个可视化的状态机设计器方便用户使用:

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

在这里插入图片描述

3.2 最佳实践

本示例未安装最佳实践进行,请自行创建事务控制表,参照:Seata TCC 模式下解决幂等、悬挂、空回滚问题 | Spring Cloud56 借鉴其思路,自行实践。

3.2.1 允许空补偿

  • 空补偿:原服务未执行,补偿服务执行了
  • 出现原因:
    • 原服务 超时(丢包)
    • Saga 事务触发 回滚
    • 未收到 原服务请求,先收到 补偿请求

所以服务设计时需要允许空补偿,即没有找到要补偿的业务主键时返回补偿成功并将原业务主键记录下来。

3.2.2 防悬挂控制

  • 悬挂:补偿服务 比 原服务 先执行
  • 出现原因:
    • 原服务 超时(拥堵)
    • Saga 事务回滚,触发 回滚
    • 拥堵的 原服务 到达

所以要检查当前业务主键是否已经在空补偿记录下来的业务主键中存在,如果存在则要拒绝服务的执行。

3.2.3 幂等控制

  • 原服务与补偿服务都需要保证幂等性,由于网络可能超时,可以设置重试策略,重试发生时要通过幂等控制避免业务数据重复更新。

3.2.4 缺乏隔离性的应对

  • 由于 Saga 事务不保证隔离性,在极端情况下可能由于脏写无法完成回滚操作, 比如举一个极端的例子,分布式事务内先给用户A充值, 然后给用户B扣减余额, 如果在给A用户充值成功, 在事务提交以前,A用户把余额消费掉了, 如果事务发生回滚, 这时则没有办法进行补偿了。这就是缺乏隔离性造成的典型的问题,实践中一般的应对方法是:
    • 业务流程设计时遵循“宁可长款, 不可短款”的原则,长款意思是客户少了钱机构多了钱,以机构信誉可以给客户退款,反之则是短款,少的钱可能追不回来了。所以在业务流程设计上一定是先扣款。
    • 有些业务场景可以允许让业务最终成功,在回滚不了的情况下可以继续重试完成后面的流程,所以状态机引擎除了提供“回滚”能力还需要提供“向前”恢复上下文继续执行的能力,让业务最终执行成功,达到最终一致性的目。

3.2.5 性能优化

  • 配置客户端参数client.rm.report.success.enable=false,可以在当分支事务执行成功时不上报分支状态到server,从而提升性能。

    当上一个分支事务的状态还没有上报的时候,下一个分支事务已注册,可以认为上一个实际已成功

四、示例说明

基于Seata Saga模式,演示分布式事务的提交和回滚。

本示例是一个商品下单的案例,一共有三个服务和一个公共模块:

  • order-saga:业务服务,用户下单操作将在这里完成。
  • account-saga:账户服务,可以查询/修改用户的账户信息
  • storage-saga:仓储服务,可以查询/修改商品的库存数量。
  • common-tcc:公共模块,包含:实体类、openfeign接口、统一异常处理等。

示例状态图如下:

在这里插入图片描述

一个分布式事务内会有3Saga事务参与者,分别是:AccountServiceStorageServiceOrderService,其中:

  • AccountServiceStorageService为本地Bean(调用远程http服务),都有一个reduce方法,表示余额扣减或库存扣减或,还都有一个compensateReduce方法,表示补偿余额或库存扣减
  • OrderService为本地Bean,有一个createOrder方法,表示创建订单记录,还有一个compensateOrder方法,表示补偿(移除)订单记录

4.1 状态机构建

4.1.1 状态机完整json

对应的完整状态机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": "PURCHASE_FAILED",
        "Message": "purchase 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

在这里插入图片描述
说明:

  • StateMachine下配置的是"状态机" 属性
    • Name:表示状态机的名称,必须唯一
    • Comment:状态机的描述
    • Version:状态机定义版本
  • Next:服务执行完成后下一个执行的"状态"(下一个执行的"状态"节点的ID

4.1.3 任务节点ServiceTask

在这里插入图片描述
完整Props内容:

{
  "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": []
}

说明:

  • ServiceName:服务名称,通常是服务的beanId

  • ServiceMethod:服务方法名称

  • CompensateState:该"状态"的补偿"状态"(补偿"状态"节点的ID

  • Input:调用服务的输入参数列表, 是一个数组,对应于服务方法的参数列表,$.表示使用表达式从状态机上下文中取参数,表达使用的SpringEL,如果是常量直接写值即可。

  • Output:将服务返回的参数赋值到状态机上下文中, 是一个map结构,key为放入到状态机上文时的key(状态机上下文也是一个map),value$.是表示SpringEL表达式,表示从服务的返回参数中取值,#root表示服务的整个返回参数

  • Status: 服务执行状态映射,框架定义了三个状态,SU 成功、FA 失败、UN 未知,我们需要把服务执行的状态映射成这三个状态,帮助框架判断整个事务的一致性,是一个map结构,key是条件表达式,一般是取服务的返回值或抛出的异常进行判断,默认是SpringEL表达式判断服务返回参数,带$Exception{...}开头表示判断异常类型。value是当这个条件表达式成立时则将服务执行状态映射成这个值

  • Retry: 捕获异常后的重试策略,是个数组可以配置多个规则,Exceptions 为匹配的的异常列表, IntervalSeconds 为重试间隔, MaxAttempts 为最大重试次数,BackoffRate 下一次重试间隔相对于上一次重试间隔的倍数,比如说上次一重试间隔是2秒,BackoffRate=1.5 则下一次重试间隔是3秒。Exceptions 属性可以不配置,不配置时表示框架自动匹配网络超时异常。当在重试过程中发生了别的异常,框架会重新匹配规则,并按新规则进行重试,同一种规则的总重试次数不会超过该规则的MaxAttempts

    "Retry": [
        {
            "Exceptions": ["io.seata.saga.engine.mock.DemoException"],
            "IntervalSeconds": 1.5,
            "MaxAttempts": 3,
            "BackoffRate": 1.5
        },
        {
            "IntervalSeconds": 1,
            "MaxAttempts": 3,
            "BackoffRate": 1.5
        }
    ]
    
  • Next:服务执行完成后下一个执行的"状态"(下一个执行的"状态"节点的ID

  • IsForUpdate:标识该服务会更新数据, 默认是false,如果配置了CompensateState则默认是true,有补偿服务的服务肯定是数据更新类服务

  • IsPersist:执行日志是否进行存储,默认是true,有一些查询类的服务可以配置为false,执行日志不进行存储提高性能,因为当异常恢复时可以重复执行

  • IsAsync:异步调用服务,注意:因为异步调用服务会忽略服务的返回结果,所以用户定义的服务执行状态映射(下面的Status属性)将被忽略,默认为服务调用成功, 如果提交异步调用就失败(比如线程池已满)则为服务执行状态为失败

  • IsRetryPersistModeUpdate:向前重试时,日志是否基于上次失败日志进行更新,默认是false,即新增一条重试日志 (优先级高于状态机属性配置)

  • IsCompensatePersistModeUpdate:向后补偿重试时,日志是否基于上次补偿日志进行更新,默认是false, 即新增一条补偿日志 (优先级高于状态机属性配置)

  • Loop:标识该事务节点是否为循环事务,即由框架本身根据循环属性的配置,遍历集合元素对该事务节点进行循环执行。具体使用见:http://seata.io/zh-cn/docs/user/saga.html

当没有配置Status对服务执行状态进行映射, 系统会自动判断状态:

  • 没有异常则认为执行成功
  • 如果有异常,则判断异常是不是网路连接超时, 如果是则认为是FA
  • 如果是其它异常,服务IsForUpdate=true则状态为UN, 否则为FA

整个状态机的执行状态如何判断?
是由框架自己判断的, 状态机有两个状态: status(正向执行状态), compensateStatus(补偿状态)

  • 如果所有服务执行成功(事务提交成功)则status=SU, compensateStatus=null
  • 如果有服务执行失败且存在更新类服务执行成功且没有进行补偿(事务提交失败) 则status=UN, compensateStatus=null
  • 如果有服务执行失败且不存在更新类服务执行成功且没有进行补偿(事务提交失败) 则status=FA, compensateStatus=null
  • 如果补偿成功(事务回滚成功)则status=FA/UN, compensateStatus=SU
  • 发生补偿且有未补偿成功的服务(回滚失败)则status=FA/UN, compensateStatus=UN
  • 存在事务提交或回滚失败的情况Seata Sever都会不断发起重试

4.1.4 选择节点Choice

在这里插入图片描述
说明:
Choice类型的"状态"是单项选择路由 Choices

  • 可选的分支列表,只会选择第一个条件成立的分支 ExpressionSpringEL表达式
  • Next:服务执行完成后下一个执行的"状态"(下一个执行的"状态"节点的ID

重要:下面是choice线条默认的属性,如果不修改配置会出现异常,正确为Props:{}

在这里插入图片描述

4.1.5 成功节点Succeed

在这里插入图片描述

说明:

  • 运行到"Succeed状态"表示状态机正常结束,,正常结束不代表成功结束,是否成功要看每个"状态"是否都成功

4.1.6 失败节点Fail

在这里插入图片描述

说明:

  • 运行到"Fail状态"状态机异常结束,异常结束时可以配置ErrorCodeMessage,表示错误码和错误信息,可以用于给调用方返回错误码和消息

4.1.7 补偿触发节点CompensationTrigger

在这里插入图片描述

说明:

  • CompensationTrigger类型的state是用于触发补偿事件,回滚分布式事务
  • Next:服务执行完成后下一个执行的"状态"(下一个执行的"状态"节点的ID

4.1.8 补偿节点Compensation

在这里插入图片描述

说明:

  • ServiceName:服务名称,通常是服务的beanId
  • ServiceMethod:服务方法名称
  • Input:调用服务的输入参数列表, 是一个数组,对应于服务方法的参数列表,$.表示使用表达式从状态机上下文中取参数,表达使用的SpringEL,如果是常量直接写值即可。

4.1.9 异常捕获节点

在这里插入图片描述
在这里插入图片描述

说明:

  • 注释 1 异常捕捉节点要通过图形覆盖在状态任务节点的图形上,实现2者之间的关联
  • 注释 2 通过对Catch节点发散出去的箭头线上的属性配置,指定对什么异常进行捕获,以及捕获到对应的异常后,指定下一个执行的"状态"(下一个执行的"状态"节点的ID

4.1.10 如何触发补偿

为了提高灵活性。用户可以自己控制是否进行回滚,因为并不是所有异常都要回滚,可能有一些自定义处理手段:

  • 补偿节点不能够自动触发补偿,对需要补偿的必须手动在状态机json中,由 Catch 或者 Choices 属性路由到 CompensationTrigger,或是参照章节4.1.9的实现方式

4.1.11 复杂参数的Input定义

复杂参数示例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"
    }
}

上面的complexParameterMethod方法定义如下:

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;
}

启动状态机时传入参数:

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);

注意:ParameterTypes属性是可以不用传的,调用的方法的参数列表中有MapList这种可以带泛型的集合类型,因为java编译会丢失泛型,所以需要用这个属性,同时在Inputjson中对应的对这个json@type来申明泛型(集合的元素类型)

4.1.12 Saga的json文件热部署

通过:stateMachineEngine.getStateMachineConfig().getStateMachineRepository().registryByResources()。不过java代码和服务需要自己实现支持热部署。

4.1.13 未完成的状态机实例恢复

Seata Saga 开启事务的客户端或者Seata Server服务端宕机或者重启,未完成的状态机实例,通过读取本地数据库有记录日志,通过日志恢复。Seata Server会触触发事务恢复。

4.2 Saga状态机所需表结构

脚本所在GitHub地址:https://github.com/seata/seata/tree/1.6.1/script/client/saga/db

建表语句如下(MySQL 语法):

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;

因篇幅原因本章就先到这里,具体示例代码搭建请见下回分解。

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

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

相关文章

当我用AI为开发AntV图表插上想象的翅膀后

前言 做前端图表时&#xff0c;最耗时的就是找配置参数&#xff0c;比如你在使用AntV G2时&#xff0c;为了更加美观&#xff0c;拉大数据之间的差距&#xff0c;需要将y轴设置一个最小值&#xff0c;由于每个图表的参数少说十几个&#xff0c;多达二十多个&#xff0c;一个一…

C# 反射 (Reflection) 的常用功能

目录 一、概述 二、实例化类 三、反射赋值 四、获取字段值 五、获取字段名 六、获取字段类型 七、调用方法 结束 一、概述 反射指程序可以访问、检测和修改它本身状态或行为的一种能力。 程序集包含模块&#xff0c;而模块包含类型&#xff0c;类型又包含成员。反射则…

华为认证 | HCIE-Datacom 考试大纲

01 HCIE-Datacom考试概述 02 HCIE-Datacom考试内容 HCIE-Datacom V1.0 考试覆盖数据通信领域&#xff1a;路由交换高阶技术、企业网络架构全景、园区网络典型架构与技术、华为CloudCampus解决方案设计与部署、广域互联网络典型架构与技术、华为SD-WAN解决方案设计与部署、广域…

人机接口回路原理(四)

五、硬件时钟电路 接口插件设置了一个硬件时钟电路&#xff0c;由一片MC146818时钟芯片及辅助元器件组成&#xff0c;如图1&#xff0d;35所示。 MC146818芯片是智能式硬件时钟&#xff0c;其内部由电子时钟和存储器两部分组成。可计年、月、日、时、分、秒、星期&#xff1b;能…

时间序列中的无监督表示学习

自监督学习中&#xff0c;有一个常用的方法是对比学习&#xff1b; 2.  时间序列的表示学习 1.1 采用对比学习的方式 Time-series representation learning via temporal and contextual contrasting(IJCAI’21) 本文采用对比学习的方式进行时间序列表示学习。首先对于同一…

告别原始 UI 样式,拥抱 Fluent Design 风格 PyQt/PySide 组件库

简介 这是一个使用 PyQt/PySide 编写的 Fluent Design 风格的组件库&#xff0c;支持亮暗主题无缝切换和自定义主题色&#xff0c;搭配 QtDesigner 可以快速开发美观的界面。github 仓库地址为 https://github.com/zhiyiYo/PyQt-Fluent-Widgets &#xff0c;演示视频可以在哔哩…

夏至后,这些农事活动要注意管理

夏至过后&#xff0c;温度会进一步攀登&#xff0c;较高的气温和光照会让夏作物生长更加旺盛。接下来就让我们看看在这些夏作物在种植时都需要注意哪些方面吧&#xff01; 一、蔬菜管理 夏白菜、夏甘蓝、夏菜花都是在6月下旬开始育早熟的菜花苗&#xff0c;秋大棚中的芹菜也是…

超强总结,性能测试实战(购物业务板块)真实压测场景...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 购物网站购物流程…

人工智能(pytorch)搭建模型13-pytorch搭建RBM(受限玻尔兹曼机)模型,调通模型的训练与测试

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能(pytorch)搭建模型13-pytorch搭建RBM(受限玻尔兹曼机)模型&#xff0c;调通模型的训练与测试。RBM(受限玻尔兹曼机)可以在没有人工标注的情况下对数据进行学习。其原理类似于我们人类学习的过程&#xff0c…

Redis简单动态字符串SDS

目录 前言 一.SDS定义 二.SDS与C字符串的区别 2.1 常数复杂度获取字符串的长度 2.2 杜绝缓冲区溢出 2.3 减少修改字符串时带来的内存重分配次数 2.3.1 空间预分配 2.3.2 惰性空间释放 2.4 二进制安全 2.5 兼容部分C字符串函数 2.6 总结 三.SDS缺点 前言 Redis没有直接使用C语…

gRPC教程与应用

gRPC是是谷歌一个开源的跨语言的RPC框架&#xff0c;面向移动和 HTTP/2 设计。 grpc中文网 在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法&#xff0c;使得您能够更容易地创建分布式应用和服务。 gRPC 也是基于以下理念&#xff1…

python3+requests+unittest接口自动化测试

1.环境准备 python3 pycharm编辑器 2.框架目录展示 &#xff08;该套代码只是简单入门&#xff0c;有兴趣的可以不断后期完善&#xff09; &#xff08;1&#xff09;run.py主运行文件&#xff0c;运行之后可以生成相应的测试报告&#xff0c;并以邮件形式发送&#xff1b;…

【C++进阶】红黑树实现

文章目录 红黑树的概念红黑树的性质红黑树节点的定义红黑树结构红黑树的插入1.按照二叉搜索的树规则插入新节点2.进行旋转和变色源码 红黑树的验证中序遍历判断是否满足二叉搜索树判断是否满足红黑树 完整源码 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但…

基于spss的多元统计分析 之 单/双因素方差分析 多元回归分析(1/8)

实验目的&#xff1a; 1&#xff0e;掌握单样本t检验、两样本t检验、配对样本t检验、单因素方差分析、多元回归分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件或者R软件关于单因素、多因素方差分析、多元回归分析的基本操作&#xff1b; 3&#xff0e;利用实验指导…

2.3C++保护成员

C 保护成员 在C中&#xff0c;可以使用保护成员 protected&#xff0c;来提高代码的安全性。 我用大白话解释一下什么是保护成员&#xff1a;说白了就是为了防止其他类直接访问或修改其成员加的一个措施。 目的是保护&#xff0c;成员的私有性和可见性。 C 类的保护 可以为…

web 语音通话 jssip

先把封装好的地址安上&#xff08;非本人封装&#xff09;&#xff1a;webrtc-webphone: 基于JsSIP开发的webrtc软电话 jssip中文文档&#xff1a;jssip中文开发文档&#xff08;完整版&#xff09; - 简书 jssip使用文档&#xff1a;&#xff08;我没有运行过&#xff0c;但…

Nginx服务器,在window系统中的使用(前端,nginx的应用)

简介&#xff1a;Nginx是一个轻量级、高性能的HTTP和反向代理web服务器&#xff0c;且支持电子邮件&#xff08;IMAP/POP3&#xff09;代理服务&#xff0c;特点是占用内存少&#xff0c;并发能力强&#xff0c;给我们来了很多的便利&#xff0c;国内大部分网站都有使用nginx&a…

18款奔驰S350升级后排座椅记忆功能,提升您乘坐舒适性

带有记忆功能的座椅可以存储三个的座椅设置以及行车电脑中的舒适性设置。只要按一下按钮就可以跳到记忆模式&#xff0c;让座椅回到上一次设置。

使用 BigQuery Omni,发现跨云地理空间分析的优势

【本文由 Cloud Ace 整理发布。Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌云认证…

第十章详解synchronized锁升级

文章目录 升级的流程为什么要引入锁升级这套流程多线程访问情况具体流程 轻量级锁如何使用CAS实现轻量级锁CAS加锁成功CAS加锁失败CAS进行解锁 总结何时变为重量级锁 锁膨胀自旋优化 偏向锁主要作用偏向状态测试撤销偏向锁 撤销 - 调用对象 hashCode撤销 - 其它线程使用对象撤销…