什么是幂等性?
在HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对资源本身应该具有同样的副作用(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
- 幂等不仅仅只是一次(或多次)请求对资源没有副作用(如查询数据库的操作,没有增删改,因此没有对数据库有任何影响)。
- 幂等还包括第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会对资源产生副作用。
- 幂等关注的是以后的多次请求是否对资源产生副作用,而不关注结果。
- 网络超时等问题,不是幂等的讨论范围。
幂等性是系统服务对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部电泳失败是常态,并且失败之后必然会有重试。
什么情况下需要幂等?
在业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而造成重复提交情况。
在交易系统、支付系统中这种重复提交造成的问题尤其明显,如:
- 用户在app上连续点击了多次提交订单,后台应该只产生一个订单
- 向支付系统发起支付请求,由于网络问题或系统BUG重发,支付系统应该只扣一次钱
很显然,声明幂等的服务认为,外部调用者会存在多次调用的情况,为了防止外部多次调用使系统数据状态发生多次改变,将服务设计成幂等。
幂等与防重
例子:小明在双十一时点击提交订单按钮,选择在线支付,点击了确认支付按钮,但这时候网络有点慢,小明担心商品被抢完,于是就点击了多次确认支付按钮,如果这个订单被扣款多次,客服就会被小明打爆。
在这个例子中,小明的问题,只是重复提交的情况,和服务幂等的初衷是不同的。重复提交是在第一次请求已经成功的情况下,人为的进行多次操作,导致不满足幂等要求的服务多次改变状态。而幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求是否成功,却不会因多次请求而出现多次的状态变化。
什么情况下需要保证幂等性?
以下的三种场景中,只有第三种需要开发人员使用其他策略保证幂等性。
SELECT coll FROM tab1 WHERE col2=2
,无论执行多少次都不会改变状态,是天然的幂等。UPDATE tab1 SET coll=1 WHERE col2=2
,无论执行成功多少次状态都是一致的,也是幂等操作。UPDATE tab1 SET coll=coll+1 WHERE col2=2
,每次执行的结果都会发生变化,不是幂等操作。- HTTP中
GET
请求是幂等操作。
保证幂等的策略
- 乐观锁
- 设计表结构时使用乐观锁,一般通过version来做乐观锁,这样既能保证执行效率,又能保证幂等。
- 例如:
UPDATE tab1 SET coll=1,version=version+1 WHERE version=#version#
- 虽然乐观锁会存在ABA问题,但是version是自增的话就不会出现ABA问题了。
- 防重表
- 使用订单号orderId作为去重表的唯一索引,每次请求都根据订单号向去重表中插入一条数据。第一次请求查询订单支付状态,当然订单没有支付,进行支付操作,无论成功与否,执行完后更新订单状态为成功或失败,删除去重表中的数据。后续的订单因为表中唯一索引而插入失败,则返回操作失败,直到第一次的请求完成(成功或失败)。可以看出防重表作用是加锁的功能。
- 分布式锁
- 这里使用的防重表可以使用分布式锁代替,比如Redis。订单发起支付请求,支付系统会去Redis缓存中查询是否存在该订单号的Key,如果不存在,则向Redis增加Key为订单号。查询订单支付已经支付,如果没有则进行支付,支付完成后删除该订单号的Key。通过Redis做到了分布式锁,只有这次订单订单支付请求完成,下次请求才能进来。相比去重表,将放并发做到了缓存中,较为高效。思路相同,同一时间只能完成一次支付请求。
- token
处理流程:- 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间。
- 提交后后台校验token,同时删除token,生成新的token返回。
注意:redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用