一 . 导读
为什么为想起来贡献 pr 呢?在写这个 pr 之前,研究了一个多月 seata 源码,看的多了手就痒了,恰好又看到了社区的 issues ,于是就动手了。
二. pr 相关
pr 内容:参与方回滚,地址。由参与方通知TC回滚,可以提前结束全局事务并释放锁,提高seata事务的吞吐量;至于发起方可以接收参与方抛出的约定异常,不再通知TC,直接结束。
这里简单介绍一下现如今的做法,只有发起方才能发起回滚的流程:
1.被 @GlobalTransactional 注解的方法都会进入模板类进行处理。如果参与方抛出了异常,发起方这里会调用 completeTransactionAfterrThrowing(txInfo, tx, ex) 进行异常相关的处理。
2.这里判断如果是需要回滚的异常,则通过 rpc 通知 tc 进行全局事务的回滚。并且向上层抛指定 code 为 RollbackFailure 的异常。
3.这里判断是何种行为的异常,然后执行对应的处理方法。
4.如果事务的角色是参与方,则直接返回。参与方没有向 tc 上报发起全局回滚的职责。这里的方法在上图标记为 3 的地方调用。
三. 实现 pr
第一个版本设计的很简单,去掉发起方才能回滚的限制,然后增加一种 code :ParticipantReportRollback ,如果接收到这种异常的 code,interceptor 处理完抛异常给上层。
经过社区大佬提醒,意识到这么做有几个问题:
1.code 只增加了一种,只考虑参与方上报的情况,没有意识到参与方上报成功和失败是两种情况。
2.异常有很多种,有的是 seata 自身流程处理产生的异常,有的是使用者的业务异常,最终 interceptor 抛给上层的,应该是阻止流程进行下去的异常。
3.rpc 框架会拦截异常。
前两个问题挺好解决的,因为只要 pr 的思路没问题,其他情形完善一下就 ok 了。
为了防止思考的不全面,我还写了一个很多个逻辑分支的测试类:
-》表示抛异常,参与方 -》参与方-》发起方,参与方-》发起方,发起方-》。每一种都分两种情况,回滚成功和回滚失败。
第三个问题就很棘手,不同的 rpc 框架对待异常的行为不一样,比如 feign,接受 500 时,会包装成 FeignException ,再抛给消费者。所以这里根据异常的类型来处理就不行了。
一般 rpc 框架可能不会把消费者异常还原成提供者抛出的异常,但是一般 Exception 中的 message 会保留下来,通过 try 是可以获取到的。FeignException 就保留了 message 放在 byte[] 数组中。
所以如果要解决这个问题,就需要解析 rpc 框架的异常,但是每种 rpc 框架的异常都不一样,这就很棘手了。
四 .总结
pr 虽然还未合并,但已收获良多。写业务代码绕不开事务,分布式项目就绕不开分布式事务,好好研究一下 seata 源码还是很有必要的。