后端程序员必须知道的接口幂等性

前言:

最近负责的项目出了一个问题,用户操做回退失效。

本来的逻辑设计中,操做回退是须要回到操做前的状态。前端

通过查看日志发现,用户以前的操做作了两次,也就是说提交操做的接口被调用了两次,致使用户上一次的状态和这一次的状态是同样的,因此操做回退是没有问题的,问题出在了操做的接口被调用了两次java

对于防止重复提交,通常是放在前端页面控制的,用户点击完按钮以后,后台返回成功的结果,按钮就不可见,实践证实,客户端的限制操做不是绝对可靠的。

针对上面的场景问题,进而引起了下文的内容;本文主线:redis

①、什么是接口幂等性?数据库

②、为何会产生接口幂等性问题?后端

③、如何保证接口幂等性?浏览器

什么是接口幂等性?

首先看看幂等性的概念:

幂等性本来是数学上的概念,用在接口上就能够理解为:同一个接口,屡次发出同一个请求,必须保证操做只执行一次。 调用接口发生异常而且重复尝试时,老是会形成系统所没法承受的损失,因此必须阻止这种现象的发生。服务器

好比下面这些状况,若是没有实现接口幂等性会有很严重的后果: 支付接口,重复支付会致使屡次扣钱 ;订单接口,同一个订单可能会屡次建立。网络

图片1;session

为何会产生接口幂等性问题?

那么,什么状况下,会产生接口幂等性的问题呢?下面简要介绍下几种状况:多线程

  • 网络波动, 可能会引发重复请求
  • 用户重复操做,用户在操做时候可能会无心触发屡次下单交易,甚至没有响应而有意触发屡次交易应用
  • 使用了失效或超时重试机制(Nginx重试、RPC重试或业务层重试等)
  • 页面重复刷新
  • 使用浏览器后退按钮重复以前的页面操做,致使重复提交表单
  • 使用浏览器历史记录重复提交表单
  • 浏览器重复的HTTP请求
  • 定时任务重复执行
  • 用户双击提交按钮
  • . . . . . .等等

如何保证接口幂等性?

那么最关键的来了,如何保证接口幂等性?

解决办法主要分为两个方向:

  • 一个方向是客户端防止重复调用
  • 一个是服务端进行校验

上面两种方案,客户端防止重复提交实现起来比较简单,可是客户端的限制操做不是绝对可靠,因此须要服务端同时进行校验,双重防御实现绝对可靠。

下面就来聊聊保证接口幂等性的具体方案;
按钮只可操做一次:

通常是提交后把按钮置灰或loding状态,消除用户由于重复点击而产生的重复记录,好比添加操做,因为点击两次而产生两条记录。

token机制:

功能上容许重复提交,但要保证重复提交不产生反作用,好比点击n次只产生一条记录,具体实现就是进入页面时申请一个token,而后后面全部的请求都带上这个token,后端根据token来避免重复请求。

图片2;

使用Post/Redirect/Get模式:

在提交后执行页面重定向,这就是所谓的Post-Redirect—Get(PRG)模式,简单来讲就是当用户提交连表单后,跳转到一个重定向的信息页面,这样就避免用户按F5刷新致使的重复提交,并且也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退致使一样重复提交的问题。

在session存放特殊标志:

在服务端,生成一个惟一的标识符,将它存入session,同时前端获取这个标识符的值将它写入表单的隐藏中,用于用户输入信息后点击一块儿提交,在服务器端,获取表单中隐藏字段的值,与session中的惟一标识符比较,相等说明是首次提交,就处理本次请求,而后将session中的惟一标识符移除,不相等则表示是重复提交,再也不作处理。

使用惟一索引防止新增脏数据:

利用数据库惟一索引机制,当数据重复时,插入数据库会抛出异常,保证不会出现脏数据。

乐观锁:

若是更新已有数据,能够进行加锁更新,也能够设计表结构时使用乐观锁,经过version来作乐观锁,这样既能保证执行效率,又能保证幂等;

乐观锁的version版本在更新业务数据要自增 update table set version = version + 1 where id = #{id} and version = #{version} ;

当有重复请求的时候,第一个请求会获取当前商品的version版本号,获得的version为1,紧接着因为第一个请求还没更新商品的version,第二个请求获取的version依然也是1 ;

这时候第一个请求操做更新的时候带上version并做为条件而且自增更新,这时候商品的version就会变成2,当第二个请求去操做更新的时候明显version不一致致使更新失败。

select + insert or update or delete :

该方案就是操做以前先查询一下,符合要求再插入,该方案在没有并发的系统中能够解决幂等问题,在单JVM有并发的时候能够用JVM加锁来保证幂等性,在分布式环境它是没法保证幂等性,可使用分布式锁来保证。

分布式锁:

若是是分布是系统,构建全局惟一索引比较困难,例如惟一性的字段无法肯定,这时候能够引入分布式锁;

经过第三方的工具(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,而后作操做,以后释放锁,这样实际上是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。

要点:某个长流程处理过程要求不能并发执行,能够在流程执行以前根据某个标志(用户ID+后缀等)获取分布式锁,其余流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁。

状态机幂等:

在设计单据相关的业务,或者是任务相关的业务,确定会涉及到状态机(状态变动图),就是业务单据上面有个状态,状态在不一样的状况下会发生变动;

通常状况下存在有限状态机,这时候,若是状态机已经处于下一个状态,这时候来了一个上一个状态的变动,理论上是不可以变动的,这样的话,保证了有限状态机的幂等。

注意:订单等单据类业务,存在很长的状态流转,必定要深入理解状态机,对业务系统设计能力提升有很大帮助 。

防重表:

以支付为例: 使用惟一主键去作防重表的惟一索引,好比使用订单号做为防重表的惟一索引;

每一次请求都根据订单号向防重表中插入一条数据,插入成功说明能够处理后面的业务,当处理完业务逻辑以后删除防重表中的订单号数据,后续若是有重复请求,则会由于防重表惟一索引缘由致使插入失败,直接返回操做失败,直到第一次请求返回结果;

能够看出防重表做用就是加锁的功能。 注: 最好结合状态机幂等先判断一下

缓冲队列:

将请求都快速地接收下来后放入缓冲队列中,后续使用异步任务处理队列中的数据,过滤掉重复的请求,该解决方案优势是同步处理改为异步处理、高吞吐量,缺点则是不能及时地返回请求结果,须要后续轮询得处理结果。

全局惟一号:

好比经过source来源 + 惟一序列号传入给后端,后端来判断请求是否重复,在并发时只能处理一个请求,其余相同并发请求要么返回请求重复,要么等待 前面请求执行完成后再执行。

❤ 点赞 + 评论 + 转发 哟

您能够VX搜索【木子雷】公众号,坚持高质量原创java技术文章,福利多多哟!

相关文章
相关标签/搜索