Seata解析-saga模式介绍

x33g5p2x  于2021-12-21 转载在 其他  
字(4.3k)|赞(0)|评价(0)|浏览(667)

本文基于seata 1.3.0版本

1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas的论文,讲述了saga模式如何处理长事务。
关于saga的介绍请大家参见文章,写的非常详细:

分布式事务:Saga模式

saga提供了两种实现方式,一种是编排,另一种是控制。seata的实现方式是后者。seata的控制器使用状态机驱动事务执行。
同AT模式,在saga模式下,seata也提供了RM、TM和TC三个角色。TC也是位于sever端,RM和TM位于客户端。TM用于开启全局事务,RM开启分支事务,TC监控事务运行。
在使用saga模式前,我们需要先定义好状态机,seata提供了网址可以可视化编辑状态机:

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

编辑好后,将状态机以JSON格式导出文件(导出还需要我们手工编辑,我没有找到通过页面直接导出的方式)。seata提供了DbStateMachineConfig类解析状态机文件,并将解析好的内容写入数据库。这样状态机的定义以后可以直接从数据库获取。
在saga模式下,一个状态机实例就是一个全局事务,状态机中的每个状态是分支事务。
下面是seata提供的JSON格式的状态机定义例子:

  1. {
  2. "Name": "reduceInventoryAndBalance",//定义状态机的名字
  3. "Comment": "reduce inventory then reduce balance in a transaction",
  4. "StartState": "ReduceInventory",//定义状态机的初始状态,也就是状态机开始运行时第一个执行的状态
  5. "Version": "0.0.1",
  6. //下面是状态的定义
  7. "States": {
  8. "ReduceInventory": {
  9. "Type": "ServiceTask",//状态类型,该类型状态表示一个分支事务
  10. "ServiceName": "inventoryAction",//调用的bean对象名,当前版本只支持spring容器的bean
  11. "ServiceMethod": "reduce",//调用的方法
  12. "CompensateState": "CompensateReduceInventory",//如果事务需要回滚,那么就调用该补偿状态
  13. "Next": "ChoiceState",//当前状态执行成功后,下一个需要执行的状态
  14. //执行ServiceMethod方法的入参
  15. "Input": [
  16. "$.[businessKey]",
  17. "$.[count]"
  18. ],
  19. //ServiceMethod方法的返回值
  20. "Output": {
  21. "reduceInventoryResult": "$.#root"
  22. },
  23. //判断当前状态执行后,是否成功
  24. "Status": {
  25. "#root == true": "SU",
  26. "#root == false": "FA",
  27. "$Exception{java.lang.Throwable}": "UN"
  28. }
  29. },
  30. //选择状态,该状态里面有一个判断,可以根据判断结果执行不同的状态
  31. "ChoiceState":{
  32. "Type": "Choice",
  33. "Choices":[
  34. {
  35. "Expression":"[reduceInventoryResult] == true",
  36. "Next":"ReduceBalance"
  37. }
  38. ],
  39. "Default":"Fail"
  40. },
  41. "ReduceBalance": {
  42. "Type": "ServiceTask",
  43. "ServiceName": "balanceAction",
  44. "ServiceMethod": "reduce",
  45. "CompensateState": "CompensateReduceBalance",
  46. "Input": [
  47. "$.[businessKey]",
  48. "$.[amount]",
  49. {
  50. "throwException" : "$.[mockReduceBalanceFail]"
  51. }
  52. ],
  53. "Output": {
  54. "compensateReduceBalanceResult": "$.#root"
  55. },
  56. "Status": {
  57. "#root == true": "SU",
  58. "#root == false": "FA",
  59. "$Exception{java.lang.Throwable}": "UN"
  60. },
  61. //如果当前状态执行过程中抛出异常,这里可以捕获异常,并执行Next状态
  62. "Catch": [
  63. {
  64. "Exceptions": [
  65. "java.lang.Throwable"
  66. ],
  67. "Next": "CompensationTrigger"
  68. }
  69. ],
  70. "Next": "Succeed"
  71. },
  72. "CompensateReduceInventory": {
  73. "Type": "ServiceTask",
  74. "ServiceName": "inventoryAction",
  75. "ServiceMethod": "compensateReduce",
  76. "Input": [
  77. "$.[businessKey]"
  78. ]
  79. },
  80. "CompensateReduceBalance": {
  81. "Type": "ServiceTask",
  82. "ServiceName": "balanceAction",
  83. "ServiceMethod": "compensateReduce",
  84. "Input": [
  85. "$.[businessKey]"
  86. ]
  87. },
  88. //补偿触发器状态,该状态表示状态机进入补偿,接下来要开始执行各个状态的补偿状态了
  89. "CompensationTrigger": {
  90. "Type": "CompensationTrigger",
  91. "Next": "Fail"
  92. },
  93. //下面两个状态是终止状态
  94. "Succeed": {
  95. "Type":"Succeed"
  96. },
  97. "Fail": {
  98. "Type":"Fail",
  99. "ErrorCode": "PURCHASE_FAILED",
  100. "Message": "purchase failed"
  101. }
  102. }
  103. }

下图是saga模式下状态机的运行结构:

事务运行过程中,也可能发生回滚。当全局事务需要回滚时,TC发起回滚请求,seata执行补偿状态。

当然事务执行过程中,也可以发生自动补偿。如下图:

TC通知事务回滚和事务执行失败自动补偿两个场景很类似,都是找到所有要补偿的状态,然后顺次调用对应的补偿逻辑,不同的是,第一个场景执行完所有的补偿状态就结束了,而第二个场景是执行完后还要执行CompensationTrigger类型状态的Next状态。另外补偿逻辑不能有Next状态。补偿逻辑使用的也是普通ServiceTask类型,只不过是逆向逻辑。

从上面这些可以看到,seata提供了事务执行失败后的两种处理方式,一种是不停的重试直到成功,另一种是事务回滚发起状态补偿,状态补偿是在状态机定义中设定好的。
seata在运行的过程中,创建了一个处理栈,所有将要运行的状态都在该栈中,栈顶的状态是下一个要运行的状态,当开始运行栈顶状态时,就将状态从栈中弹出。状态机初始运行时,栈中只有一个初始状态,初始状态运行完毕后切换到下一状态,这时seata将下一状态装入栈中,控制器接下来执行该状态。
在saga模式中,控制器不在TC端,而在应用端,TC只是起到监控的作用,比如监控事务执行是否超时,记录事务执行进度等。应用端通过调用状态机引擎的start方法启动状态机运行,接下来状态机可以根据定义自动切换状态直到事务执行结束。
seata运行时,在数据库中创建seata_state_inst、seata_state_machine_def、seata_state_machine_inst三张表,作用分别是保存状态实例、保存状态机定义、保存状态机实例,其中seata_state_machine_def表的数据是在启动的时候或者启动前写入数据库的,其他两张表的数据都是在事务开始运行后保存到数据库。

相关文章