分布式事务,缺陷方案咋那么多?

架构之道与术

共 1623字,需浏览 4分钟

 · 2021-08-20

说到分布式事务,可能很多时候想到的是支付转账、跨DB数据更新等典型的场景,但实际上,分布式事务是一个比高并发、高可用都普遍的多、几乎无处不在的一个问题。但因为重视程度不够,实际方案往往存在各种缺陷。


无论大公司,还是小公司,都不太可能把所有数据都存在一个DB里面,用DB的单机事务完成。实际上,都不止一个系统,系统之间需要接口互相调用。


今天就举个最简单的分布式事务例子:

你的系统,接收到用户请求,需要更新自己的DB + 调用隔壁团队的一个http接口(做数据更新操作),并且2个操作必须保证数据一致性。

(1) 是先调用别人接口,后更新DB,还是反过来?

(2) 2个操作中,1个成功,1个失败,给用户是返回成功,还是失败?


先说一下常用的缺陷方案:


缺陷方案1:把接口调用包在DB事务里面

调用接口成功,DB事务提交;调用接口失败,DB事务回滚。看起来很正确,存在问题:

(1)接口超时,那DB事务是应该提交,还是回滚?答案:2个都不对,不确定。

(2)调用接口block,导致DB事务一直卡在那,锁没有办法及时释放。在并发量大的情况下,DB会被拖死。


缺陷方案2:接口调用失败,DB数据做逆向操作。

先提交DB事务,然后调用外部接口。如果接口调用失败,DB数据再做逻辑上回滚。存在问题:

(1)同样,接口如果不是失败,是“超时”,也不确定DB是应该回滚,还是提交。

(2)DB逻辑回滚本身,是个网络操作。也可能失败/超时,此时如何处理?


缺陷方案3:接口调用失败,插入异常表,再事后回补。

先提交DB事务,然后调用外部接口。如果接口调用失败,往异常表插入1条记录,然后事后根据异常表里面的记录,去重试外部接口。存在问题:

同上面一样,插入异常表,是个网络操作,也没办法保证这个一定成功。


缺陷方案4:接口调用失败,往Kafka里面插入一条异常消息,消费消息做事后回补。


同方案3一样,插入异常消息也是个网络操作,有可能超时/失败。



正确的策略1:部分成功时,给调用方返回失败。让调用方重试,被调方幂等

无论是先调用外部接口,后更新DB,还是反过来,只要任何一个超时,给用户返回的不是“成功”,也不是“失败”,而是“不确定”。

所谓“不确定”,就是类似“目前网络繁忙,请稍候再次确认。有可能之前的更新成功了,也有可能更新失败了“。

所以,无论是DB更新,还是接口调用,都要保证幂等。


正确的策略2:部分成功时,给调用方返回成功。然后做最终一致性保证。

先更新DB,后调用外部接口。不管接口结果如何,只要DB成功了,就给用户返回成功。

如果接口调用失败/超时,重试3次之后,仍然失败/超时。那往异常表插入1条记录,事后再去根据异常表做补偿。

但异常表本身有可能插入失败,所以需要一个”对账任务“去做兜底,不断扫描DB,对于DB中的每条记录,查询对应的外部接口是否更新成功,如果没有更新成功,不断重试,直到成功。重试N次之后,还不行,告警人工处理。

”对账任务“可以是增量的,根据DB中数据的update_time,定期对账。


正确的策略3:DB成功了,就返回成功。然后异步去调外部接口,最终一致性。

DB成功了,就对用户返回成功。然后一个后台任务,不断去扫描DB,调对应的外部接口。接口失败/超时,后台任务可以无限次重试,直到最终2者一致。同样,超过N次之后,还不一致,告警人工处理。


最后总结:因为网络调用”超时“是不可避免的,超时后结果是”不确定“的。不管怎么做,都没有办法在1次用户请求中,保证2次网络调用同时成功。所以,要么”调用方有无限次重试 + 告警人工修补机制”,要么“被调方有无限次重试 + 告警人工修补机制”,也就是“对账兜底”。



浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报