以一个退款功能为例,如何保证退款、数据库更新、发送短信这3个层面的工作保证都做或者都不做?

如果只是一堆数据库的操作,那还可以用事务来解决。
如果涉及到的操作是多个层面的,如何保证其一致性呢?
如果按顺序一件一件地做,前一件做失败了,就不做后面的,危害会小一些。但最好还是把前面做成的事给撤销掉,而这很难,几乎做不到(退款提交了,就不可能追回退款;数据更新了,老数据就丢失了,不能恢复成原始数据;短信发出去了,也无法撤回)
更严重的是,一旦向支付方式提交退款如果成功了,而本地数据库却更新失败,没有记录到“已退款”,那必然就会形成坏账。
所以这一类问题究竟应该怎么处理才合适?
请大家就以我举例的退款问题来回答吧。

阅读 3.7k
3 个回答

所有事务都需要可回滚,像你例子中短信发了就不可能回滚,而且短信发了有没有成功,发成功了对方有没有收到?这都不是确定性的,所以,把这种加到事务一致性是不可行的。

对于可回滚的,只要做到最终一致就行,怎么做就根据各自需要决定了,比如使用MQ或者文本日志。用你的例子来说:

提交退款 -> 更新数据。

在业务逻辑上,更好的应该是 “更新数据,然后提交退款”,因为更新数据可回滚,但退款一般是不可回滚。

做法上,简单的就可以借助数据库事务来更新数据,,然后去退款,得到退款结果后,再commit数据库。

复杂的可以用二阶段提交等方式。

最后,提醒一下,要真正实现自动的最终一致,需要极高的可用性,一般场景下,记下log,偶尔有问题,通过脚本事务对帐就好了。

把可控的放在最后,把最不着急的放在最后.

不同情景会有太多情况了.
在退款这个流程中,我们可以控制的是发送短信和更新数据库.发送短信无论如何我们都能操作,哪怕发送失败了也只是稍微影响一点用户体验,所以可以放在最后的最后来做,在我们确保支付渠道退款完成,数据库更新完成之后,随便发发就好了.

微信和支付宝的退款/支付都是回调模式的,渠道完成之后才会通知客户端去修改数据库,其实就是在保护数据的一致性,因为退款是有可能失败的,渠道也要和银行接口交互,比如退款到银行卡但是该卡状态异常?账户状态异常?都有可能造成退款失败,我们没办法想当然的认为退款一定会成功,所以只能收到回调的时候修改数据库.

这么看来,我们需要保证的其实只有一点(渠道方会反复推送直到成功,网络问题可以忽略),那就是发起退款之后,保证能够正确更新数据,并且在收到回调(失败/成功)之前,禁止用户修改数据,避免数据冲突.

比如提现操作虽然还没有完成(收到回调),但是金额也要从可用余额中冻结,收到回调之后从余额中扣除,支付宝的提现在途状态就是这样

我们不得不考虑失败的情况,如果退款失败了会怎么样?

其实不会怎么样,因为我们数据库中的订单状态是处理中,还是生效状态.退款失败之后是选择自动重试还是提醒用户,按部就班就可以了.

记录日志,查询数据库更新失败原因,根据日志编写代码,修改数据并且调用发短信接口,,解决数据库记录失败问题。BUG会有的,要做的就是止损。一般金融交易都会有数据流水以及交易日志用于维护及止损。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题