每个工程师都应该了解的:幂等。
概念
幂等(Idempotency):一个操作任意多次执行所产生的影响均与一次执行的影响相同。
应用场景
举个例子:处理一次电商网站收款或者付款的交易。
当给微信支付发送这个付款请求后,一个顺利的场景是没有任何错误发生,微信支付收到付款请求后,处理所有转账,然后返回一个 HTTP 200 消息表示交易完成。
但如果发出请求后,有个请求超时,没有收到关于这个请求成功或失败的回执,比如:
- 微信支付没有收到这个请求:请求在到达微信支付端前就已经发生超时。
- 微信支付有收到这个请求,但处理失败,没有回执:请求到达微信支付端,支付交易失败,此时发生超时。
- 微信支付有收到这个请求,处理成功,但没有回执:请求到达微信支付端,支付交易成功,此时发生超时。
- 微信支付有收到这个请求,处理成功,发出回执,但客户没有收到:网络原因回执丢失,客户端超时。
这时,重新提交一次支付请求。但存在一个潜在问题:请求超时是上述哪一种,有没可能引发多次支付?
实现原理
幂等,多次执行所产生的影响均与一次执行的影响相同。即:去重。两大关键因素:
-
幂等令牌(
Idempotency Key)客户端和服务器通过什么方式来识别这实际上是同一个请求或是同一个请求的多次尝试,这往往需要双方有一个既定的协议,如:账单号或者交易令牌。这种在同一个请求上具备唯一标识的元素,通常是由客户端生成。
-
确保唯一性(
Uniqueness Guarantee)服务器端用什么机制去确保同一个请求一定不会被处理两次,最常用的做法是利用数据库。如:把幂等令牌所在的数据库表的列作为唯一性索引。这样,当试图存储两个含有同样令牌的请求时,必定有一个会报错。 但是,简单的读检查并不一定成功,因为读与读之间会有竞争条件(
Race Condition),还是有可能出错。
一个系统能正确处理和实现上述两个要素,基本就达到了幂等的要求。
常见问题
-
幂等令牌什么时候产生,怎样产生。
微信支付保证每一个请求对应的支付交易一定只会被处理一次,但是这个请求的多次重复一定要共有微信可以识别的某个标识。
假如客户端对同一笔交易的多次请求,产生的幂等令牌并不相同,那么无论其余地方多完美,都不能保证“一个操作任意多次执行所产生的影响均与一次执行的影响相同”。
-
令牌有没有被误删的可能。
生成的幂等令牌在被使用后,不小心因为数据库回滚(
DB Rollback)等原因被删除了,这个时候客户端就不知道自己其实已经发过一次请求,将有可能生成一个新的账单,并产生全新的令牌,而服务端对此一无所知。 -
各种竞争关系。
用数据库的读检查来确保唯一性经常因为竞争而不生效。
在一个需要幂等的系统中,保证唯一性的各个环节和实现,都要考虑竞争关系(
Race Condition)。 -
对请求重试的处理。
大部分是服务端要做的工作。一个常见的方法是:区分正在处理的请求、处理成功和处理失败的请求。这样当客户端重试的时候,服务端知道每一个请求的处理情况,才能根据情况或直接返回,或再次处理。
-
一个系统中需要多层幂等。
A发送请求给B,B处理的一部分是要发送请求给另一个系统C,C在处理的过程中还可能需要发请求给另一个系统D……D处理完了返回给C,C返回给B,B返回给A。在这个链条中,如果
A、B、C、D中任何一个系统没有正确实现幂等,即出现了 “幂等漏洞”,那么一次请求还是有可能被多次执行,产生区别于一次执行的影响。
小结
幂等是一个语义范畴上对行为结果的定义,只有把实现中所有的细节都做对了,才能得到想要的效果。任何一个地方设计有漏洞,或是实现上有 bug,系统都会出现这样或那样的问题。