在springboot 框架下，微服务解决方法

* Saga-server 负责记录回滚sql语句（以任务形式），并在需要的时候回滚。也可以定时或者UI界面触发

* database 负责保存事务数据以及回滚任务

* client，任何微服务客户端，通过kafka 发送事务数据。客户端在调用start时候，标记自己的事务，在commit或者rollback的时候发送回滚sql到saga server

* 当微服务调用链中最初的那个事务要求回滚（画重点)，则表示真正需要回滚，Saga—Server会发送回滚任务到各自系，进行真正回滚

* Kafka 消息机制保证了回滚任务一定能被执行。

> 注意，采用客户端异步消息方式同saga-server 交互，问题是回滚可能带来延迟。即提示用户出错，
> 但数据当时还可能没有回滚.这依赖于Saga—Server和消息服务器的性能，本示例是秒级回滚，不影响体验
  
如果不懂Saga，可以参考 https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga，我认为最好的一篇说明Saga的的文章


![](../../doc/saga-microservice.png)

* start，发送全局事务gid+纳秒时间戳 到事务管理服务器，其负责检测是否有同样的事务id，如果有，则加入，如果没有，则创建

* commit，保存所有rollback语句到服务器

* rollback， 保存所有rollback语句到服务，并设置rollback标记给服务器，
服务器接收到rollback标记，标记事务失败。但并不立即回滚。直到最外层的事务rollback标记发回才开始开始回滚，取出所同一个gid的rollback语句，然后再发回到各个客户端，客户端依次执行，并反馈结果到saga-server。
服务器会判断如果所有成功执行rollback，则标记事务成功回滚 则发回失败。 服务器接收到失败后，，标记下次回滚时间。成功的则不需要标记

* 服务器提供API查询事务和其所有回滚任务，以通过界面再次执行事务回滚或者特定的任务

关键设计：
* Saga-Server通过Kafka接受回滚任务，考虑到消息机制，因此Saga—Server可以是水平扩展，保证其工作可靠性
* 服务器和客户端通过消息队列，同样的gid，将顺序发送给同样的Saga—Server，以保证顺序。这里需要保证各个客户端的时间是一致的
* 回滚任务由各个客户端生成，beetlsql的saga mapper会自动生成操作的逆向操作（不需要解析sql来生成逆向sql）

  
Saga-Server提供Swagger API用于查询和管理回滚任任务，如下是一个查询gid为3的所有回滚任务，
可以看到有3个回滚任务，userSystem，orderSystem和demoSystem，这三个系统，demoSytem的status是Error
表示触发了回滚，3个系统的rollbackStatus都是Success，表示回滚执行成功


```json
{
  "success": true,
  "msg": "成功",
  "data": [
    {
      "id": "a717d181-608b-4f99-ab8a-9c60e83cce12",
      "gid": "123",
      "appName": "userSystem",
      "status": "Success",
      "rollbackStatus": "Success",
      "time": 1138055785025836,
      "taskInfo": "{\"tasks\":[{\"@Clazz\":\"org.beetl.sql.saga.ms.client.SagaClientTransaction$KafkaSagaTaskTrace\",\"rollbackTask\":{\"@Clazz\":\"org.beetl.sql.saga.common.ami.SagaUpdateByIdAMI$UpdateSagaRollbackTask\",\"sqlManagerName\":\"mySqlManager\",\"obj\":{\"@Clazz\":\"org.beetl.sql.saga.demo.entity.UserEntity\",\"id\":\"xiandafu\",\"name\":\"闲大赋\",\"balance\":4}},\"success\":false}],\"success\":true}",
      "createTime": 1607864163823,
      "updateTime": 1607864164067
    },
    {
      "id": "c4765a46-cf2f-4d7a-a714-dc35bf723df2",
      "gid": "123",
      "appName": "orderSystem",
      "status": "Success",
      "rollbackStatus": "Success",
      "time": 1138055465293352,
      "taskInfo": "{\"tasks\":[{\"@Clazz\":\"org.beetl.sql.saga.ms.client.SagaClientTransaction$KafkaSagaTaskTrace\",\"rollbackTask\":{\"@Clazz\":\"org.beetl.sql.saga.common.ami.SagaInsertAMI$InsertSagaRollbackTask\",\"sqlManagerName\":\"mySqlManager\",\"entityClass\":\"org.beetl.sql.saga.demo.entity.OrderEntity\",\"pkId\":\"4a40f48b-4b29-4b62-8d47-5f3867b03afd\"},\"success\":false}],\"success\":true}",
      "createTime": 1607864163570,
      "updateTime": 1607864164058
    },
    {
      "id": "48eb2fbe-9ff8-4913-bfd2-63176b3646b4",
      "gid": "123",
      "appName": "demoSystem",
      "status": "Error",
      "rollbackStatus": "Success",
      "time": 1138055258883126,
      "taskInfo": "{\"tasks\":[],\"success\":true}",
      "createTime": 1607864163543,
      "updateTime": 1607864164052
    }
  ],
  "errorCode": 0
}
```


# todo

* 要考虑Saga-Server会记录大量事务数据，甚至比业务数据还多。要考虑以gid的分库分表。或者定时清理已经完成的回滚或者是全部成功的事务，避免数据库过大
* 回滚失败后（比如数据库宕机或者主从替换中），Saga-Server不再处理，等待人工调用api再次回滚。可以考虑其它机制或者接入其它系统，专门处理回滚也失败的事务。
* 在回滚的时候，saga-server通过kafka发送回滚任务，这里不保证执行顺序，因此客户端需要考虑。比如俩个客户端同时修改一条记录。正向执行是有序的，但回滚不能保证顺序
  虽然saga-server能根据时间顺序发送回滚任务，并等待回滚成功再发送下一个，但代价太大。很容易因为一个客户端的回滚任务执行失败，导致其他任务不能执行



