谈谈web系统中的可重入,幂等性,分布式事务的那些事 – 中

幂等性

其实接着上面的例子说,幂等性也不是很难理解。当微信支付通知我们用户付款的时候,期望我们回复2个明确的结果:接受付款 Or 打回付款。

我们知道,既然微信和我们是跨网络交互的,必然存在异常,比如:请求被处理但是应答丢失了,请求没被处理,请求超时因为下游处理很慢,等等。一旦通讯异常那么这次调用是否对下游产生了影响,以及影响是什么,都是未知的,这种场景下只能进行重试。既然重试必然存在,那么当发生重试的时候,我们的程序能否始终保持一致的应答就是幂等性的典型体现。在这里,我们的应答要么总是接收付款,要么总是打回退款,不可以摇摆。

还是拿付款阶段来说,举2个典型的场景。第一种,我们成功完成了1-3步骤返回”接受付款”,但是微信没有收到应答,于是发起重试。第二种,我们在步骤3乐观锁更新失败抛出异常给微信,微信没有收到正确应答,于是发起重试。

第一种场景下,微信来重试的时候,我们查询order的status必然已经是”已付款”,此时我们要做的应该是依旧应答微信”接受付款”,这是幂等性典型的体现。

第二种场景下,微信来重试的时候,我们查询order的status已经是”过期未支付”,此时我们要做的是应答”拒绝付款”,因为首次微信调用我们抛出异常也就是没有明确答复,所以我们依旧遵循了应答的前后一致性,无论接下来微信重试多少次均会”拒绝付款”。

这里再说说红色部分”请求超时因为下游处理很慢”,这里可能触发一个场景就是第一次请求的php还没有处理完成,重试的请求又抵达了,也就是”可重入”问题也一起掺和进来了。当我们同时面临”可重入”,”幂等性”两个问题的时候,程序能否按照我们的预期继续执行呢?答案:可以正确执行。

如果读者此前没有相关的经验心得,可以先枚举一下各种时序,看一下分别更新了什么,返回了什么,这个场景不算复杂。我这里举1个时序,其他的可以自己设想一下。old-php读取到”待付款”后,crontab更新status为”过期未支付”,new-php读取到”过期未支付”,old-php乐观锁更新”已支付”没有生效抛出异常,new-php返回”拒绝付款”。

如果并发的逻辑更加复杂多样,该怎么应对呢?其实是有秘诀的,一方面是方法论正确,即提前梳理好所有状态的变迁图,每一个逻辑只管好自己的事情,遵循乐观锁思路进行编码,如果发生更新无效(或者mysql异常),通过重试(例如crontab定期扫描mysql发现异常订单)来重新执行逻辑,直到成功执行为止。另一方面,在方法论正确的场景下,多枚举不同时序的并发场景,对不自信的环节动脑琢磨,或者通过构造异常代码进行验证的方法,最终保证代码正确性。

谈谈web系统中的可重入,幂等性,分布式事务的那些事 – 下

发表评论

电子邮件地址不会被公开。