幂等性是什么
所谓幂等性,就是重复点击一个按钮或者重复发送同样的请求,不会因此而产生副作用。
比如说一个查询按钮极短的时间内重复点击或者频繁发送同样的查询参数,(不考虑性能、效率、机器损耗的情况下)是不会有副作用的。查询操作是一种天然的幂等性操作,以及把某一个值修改为固定的值,例如修改张三的年龄为18,多次调用也不会有副作用。
为什么需要幂等性
知道了幂等性是什么,我们就要保证幂等性来避免数据上出现一些错误,例如让张三的年龄+1,本来只是想调用一次的但是重复点击或重复发送了请求,就会导致+2、+3等错误数据的生成。
如何保证幂等性呢
首先重复点击这种操作,我们可以在前端就进行一个处理,例如点击后立刻禁用按钮多长时间或者弹窗确定是否提交此次操作,避免用户的一些错误操作以及系统卡顿导致的错误请求发送。
这是在浏览器侧用户操作上进行的是否发送请求的拦截,这个不是完善的策略,可能存在暴露接口的调用以及接口攻击等等。
那么在服务端我们需要怎么处理呢,有以下两种比较常见的解决方案:
1、代码逻辑判断
以常见的扣账户余额举例,某账户因为订单A所以需要扣款5元,我们可以在扣除的操作里面传递这个orderID_A,进行一个标识,如果这个订单已经操作过扣款了,那么哪怕重复发送请求或者因为服务端的响应超时重试机制,都不会再次扣款,比较简单的实现就是订单表某一个字段标识是否扣款成功。
boolean pay(int orderID, int accountID, BigDecimal amount)
再或者订单状态变更的,假设订单初始状态为0,下单成功为1,后续支付成功为2,支付失败为3等等。那么订单一定有一个过程是由0到1的update操作,并且1只能由0而来,那么我们可以在修改为1的时候,在where里面加上一个条件 AND orderStatus = 0,即只有0的状态才能变为1。
update order set orderStatus = 1 where orderID = xx and orderStatus = 0
我们可以通过服务或者数据库的返回来确定这次操作是否执行,从而推断出是否需要执行后续逻辑。
2、使用一些标识
常见的方案有前端缓存token、使用所有参数加密生成token。
前端缓存一个token:由服务端生成一个唯一的token存放在缓存,前端在调用一些需要保证幂等性的接口时,需要先去获取这个值,并且作为必填参数传递。服务端接收到这个参数后,判断是否存在是否生效,确定可用后,在缓存内删除此token(同时生成新的token供其他人使用)。如果前端提交两次或者接口提交两次这个token的话,除了第一次外,其他的请求服务端都搜索不到此token,被认为是错误的、重复的请求了,服务端不会做任何处理,这样就能保证幂等性。
使用所有参数加密生成token:将所有参数按照某种规则(暴露的接口一般会提供一个密钥之类的各自独立的参数混合加密来保证安全性)加密,生成一个sign。服务端可以短时间内缓存这个值(可以按照具体的业务分片存储,增加查询的效率),如果在短时间内还是有这个值就认为是重复请求。这种方式比较适用于要求安全性高,但并发量不高的场景。