背景

在业务开发中,我们常会面对防止重复请求的问题。当服务端对于请求的响应涉及数据的修改,或状态的变更时,可能会造成极大的危害。特别是交易系统,售后维权,以及支付系统中尤其严重。前台操作的抖动,快速操作,网络通信或者后端响应慢,都会增加后端重复处理的概率。

当然,前端能屏蔽掉重复请求(真的可以吗?前端同学回答下)你也无法逃避 timeout 这个噩梦,所以,既然重复提交无法避免,那我们就从其他地方入手,先看下一些简单的需求场景:

  1. 前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果;
  2. 我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发或系统bug重发,也应该只扣一次钱;
  3. 发送消息,也应该只发一次,同样的短信发给用户,用户会哭的;

既然调用方不靠谱,网络不靠谱,那么接口提供方就必须靠谱,允许你多次调用,我的接口都能handle,这种能力就叫幂等性。

幂等接口

幂等是一个数学概念(摘自维基百科)

  • 在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1。
  • 某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的。
  • 一元运算的定义是二元运算定义的特例。

相应地,在计算机领域,如果一个接口一次调用或多次调用的结果是一样的,那么这个接口就是幂等的。但是我没看到一个很严谨的解释,毕竟是延伸过来的说法。

所以还是列举下一些常见的幂等接口:

  1. HTTP GET
  2. SQL SELECT
  3. 大家补充…

不是幂等的接口

  1. HTTP POST
  2. SQL UPDATE
  3. 大家补充…

从这些简单的例子,我大胆归结下计算机领域幂等性是:

  1. 一次调用或多次调用对系统影响是一样的
  2. 接口返回的结果也是一样的(不受接口调用之外的影响)

好了,说完接口的幂等性,它能防止重复提交,接下来讲讲一些经典的实现方案及适用场景。

token校验

由 server 产生,并且存一份,再埋在form/cookies/header;
client 下次请求带上 token, server 校验通过后刷新 token,执行业务;

优点:client 改动很小甚至不需要改动
缺点:无法支持 client 并发请求
其他讨论:server如何存储token/如果用缓存如何设置key

唯一索引

通过把并发问题变成串行问题,如何实现很简单

  1. 数据库去重表
  2. redis 原子性
  3. MQ

加锁

乐观锁

1
update table_xxx set col=col_xxx, ver=ver+1 where ver=ver

悲观锁

1
select * from table_xxx where id = id_xxx for update

具体哪种看业务对更新成本及更新频率的考量。

状态机幂等

当业务涉及到多个状态更改,如下单支付,广告创建,可以给每一步递增标记,只允许提交大于当前标记的请求,然后每一步的幂等由上诉几种方案实现。

总结

  • 防止重复提交跟保障一致性相似,要看场景,没必要强求。
  • 幂等性应该列入程序员必备知识,特别是做 web 开发的程序员。

参考

深入理解乐观锁与悲观锁
高并发的核心技术-幂等的实现方案
系统幂等以及常用实现方式
防重复请求处理的实践与总结
分布式高并发系统如何保证对外接口的幂等性
幂等策略分析