创建订单实现幂等的一点思考

幂等的概念

大部分文章都会说,同一个操作,进行多次操作后,结果是一样的,就可以说这个操作是支持幂等的。感觉不太准确,比如一个http get操作,可能每次的结果都不一样,但是其实是幂等的。看了很多文章,感觉下面的定义比较准确:

一个操作如果多次任意执行所产生的影响(或者叫副作用),都是相同的。

创建订单的幂等

如果一个用户分两次下单,购买的商品都是一样的。

第一次请求:user1:购买一个商品product1;
第二次请求:user1:还是购买一个商品product1;

这种场景也很常见,是需要生成两个订单的。这样子看起来貌似创建订单的接口做不了幂等,因为业务数据一样的情况下,还是需要生成多个订单。但是这样子设计还是有个坑,万一创建订单的接口超时了呢?并且调用方进行了重试的话,那就可能变成用户其实想下一个单,但是订单系统其实生成了多个订单。比如说:

调用方发起创建订单的请求,订单系统收到了,并成功创建订单了。但是由于系统原因或者网络原因等,没有及时告知调用方订单已经创建成功,调用方一直等待回复,直到超时了。调用方再次发起了创建订单的请求,这个时候就可能会生成多个订单。

如果订单接口不支持幂等的情况下,如何应付这种情况呢?有两种方法

第一种:

当调用方调用订单接口超时了,是会收到异常的,这个时候调用方捕获到这个异常后,不要进行重试操作了,调用订单的一个回滚接口,将订单取消掉。虽然看起来很low,但是还是有人这么做的。

第二种:

让订单系统提供一个订单是否创建成功的查询接口,根据一些关键业务字段去查询,如果查询到已经创建成功了,则调用方不要重试了。

上面两种方案都有人用过,但是都没实现幂等。其实针对上面的场景,用幂等来设计也不是很难。可以使用一个唯一的流水号ID,用来标识是不是同一个请求或者交易。这种ID通常都需要具备全局唯一性。假设让客户端来生成这个ID,每个创建订单的请求生成一个唯一的ID。那么订单系统如何根据来实现幂等呢?通常有两种。

第一种:

先将这个ID保存到一个流水表里面,并且流水表中将这个ID设置为UNIQUE KEY,如果插入出现冲突了,则说明这个创建订单的请求已经处理过了,直接返回之前的操作结果。

第二种:

根据ID读取流水表,如果没有读取到,则创建订单和插入流水表。如果读取到了,则返回之前的操作结果。

不建议使用第二种方式,因为大部分情况下的请求都不是重试来的,让100%的请求都要去读取流水表,实在是不应该。另外,读取流水表的操作也是有潜在风险的,因为用数据库的读检查来确保数据存在性可能因为竞争而不生效,存在竞态条件。

建议用第一种方案,因为本来流水表就是要插入,顺便利用UNIQUE KEY的冲突特性来判断。

现在我们用第一种方案完整描述一下整个处理过程。

当调用方携带流水号ID调用创建订单的接口,如果出现超时了,调用方不知道订单到底创建成功还是失败,这个时候,用同一个流水号进行重试,订单系统虽然收到了两个请求,但是由于流水号ID是同一个,可以根据流水表来做幂等操作。并告知对方订单创建成功与否。

这里又有一个坑,万一调用方进行重试的时候,重新生成一个流水号,那就没得救了,会生成多个订单了。这个只能让客户端来保证了。

关于多重幂等

假设创建订单的接口在创建订单的时候,还需要依赖一些外部系统,如果订单创建接口实现了幂等,但是外部接口没有实现幂等的话,还是可能出现幂等漏洞。属于整个链路幂等的问题了。好复杂。目前还没想好如何处理这种情况呢。

思考题

调用方创建唯一ID,服务端用流水表这种方式实现幂等,非常依赖这个唯一ID。万一这个ID丢失了呢?咋破?目前我也在思考这个问题。

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

1 收藏 1 评论

关于作者:Sam哥哥

互联网技术爱好者,目前就职于广州唯品会担任JAVA高级开发。个人的csdn技术博客如下:http://blog.csdn.net/linsongbin1本人伯乐在线的文章,都出自这个csdn博客 个人主页 · 我的文章 · 15

相关文章

可能感兴趣的话题



直接登录
最新评论
  • 为什么一定要做成等幂呢?完全没有理由做成这样。

    只有在用户支付成功后才产生订单,减掉库存。

    之前用户加入购物栏,不产生订单,也不减少库存,只产生假预约,不保证用户在最后

    能购买成功。

    1,用户只有在支付

    2,产生订单

    3,减少库库

    以上3个步骤必须是在同一个事务中的原子操作。

    只要一个不成功,那就都fall back。

     

跳到底部
返回顶部