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

3,339 阅读4分钟
原文链接: blog.csdn.net

幂等的概念


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

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


创建订单的幂等


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

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

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

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

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

第一种:

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

第二种:

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

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

第一种:

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

第二种:

根据ID读取流水表,如果没有读取到,则创建订单和插入流水表。

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

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

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

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

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