一、前言
如今的网络系统,经常需要通过接口调用其它服务。例如最常见的网页浏览中的ajax请求,前端页面就需要通过接口请求后端服务。
作为开发者,在设计这些接口的时候,必须要考虑到接口的幂等性问题。接口幂等性的定义是:用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
举个简单的例子,那就是转账,用户在银行网页中填写好转账相关信息后,不小心快速点击了2次转账按钮,这时网页就会发起2次转账请求。如果不做处理,就会转了2笔钱给对方,造成损失。这就是没有保证接口幂等性的后果。
二、什么情况下需要保证接口幂等性
1)查询操作(select)
在数据不变的情况下,无论查一次还是查多次,结果都是一样的,因此select操作是天然的具有幂等性的,无需做特殊处理。
2)删除操作(delete)
无论删除一次还是删除多次,对于后端来说,最终的结果都是一样的,因此delete操作也是具有幂等性的。
3)插入操作(insert)
重复提交会导致数据库插入多条相同的数据,不具有幂等性。
4)修改操作(update)
修改在大多场景下结果一样,但是如果是增量修改是需要保证幂等性的,如下例子:
把表中 id 为 XXX 的记录的 A 字段值设置为 1,这种操作不管执行多少次都是幂等的。
把表中 id 为 XXX 的记录的 A 字段值增加 1,这种操作就不是幂等的。
三、怎么保证幂等性
如果用户是通过网页或APP等前端进行操作的话,一种基本的做法是,前端对“提交”按钮做处理:第一次点击后,按钮禁用置灰,然后提交请求给后端,如果请求成功,则执行成功逻辑;如果发生错误,则移除置灰效果,允许用户再次点击重试。这样可以防止用户误操作,短时间内连续点击多次提交按钮。
但仅仅前端做处理是不够的,后端也需要。
后端常见的3种处理方案:
1)通过数据表的唯一索引实现
这种方案适用于insert操作,例如订单创建。如果订单号是前端传过来的,我们可以给数据表的订单号字段建立唯一索引,这样当请求重复时,订单号也必然重复,数据库就会报错。
注意:建立唯一索引,会导致数据库的insert操作性能变慢,因为数据库每次插入都要检查字段值是否重复
2)通过版本号字段实现
这种方案适用于update操作,例如余额扣减。假设用户A账户的现在余额是100,要扣减50,则SQL update语句的写法有:
# 不具有幂等性的写法
UPDATE amount = amount - 50 WHERE userid = 'A';
# 具有幂等性的写法
UPDATE amount = amount - 50 WHERE userid = 'A' AND amount = 100;
可以想象到,如果采用幂等性的写法,第一次请求成功后,amount的值就会变为50,这样后面的重复请求,update语句因为 WHERE 条件不成立都不会得到执行,保证了幂等性。
这个例子中,amount字段其实是被拿来当版本号使用了,因为每次成功转账,amount字段的值都会变化。
除此之外,我们也可以给数据表加一个独立的版本号字段,每次成功update后版本号字段+1,跟上面的做法是相同的道理。
3)使用token机制实现
上面2种方案,都只是针对特定场景,不具有通用性。而token机制是具有通用性的,适用于insert/update等操作。
token机制实现步骤:
1、前端打开页面时,向后端申请一个token,token全局唯一,后端可以把token存储在redis里或其它地方,然后返回给前端
2、前端保存token
3、用户发起操作请求时,页面把相关数据以及token传给后端
4、后端检查token是否存在,存在则删除token,如果删除成功,则执行接口逻辑。如果删除token失败,说明token不存在,视业务情况直接返回失败或成功给前端。
注:这里的token其实还有一个作用,就是防止CSRF攻击