接口幂等:多次请求,结果一致。
同样的请求参数,多次去访问同一个接口,得到的结果是一致的。且服务端(针对于数据入库或数据修改)只处理一次。通俗点讲就是:防止重复提交。
往数据库添加数据
如下数据库(user),和服务端添加(user)数据接口。
假如用同样的参数去调用添加(user)接口,那么每次都会往数据库插入一条数据,那么数据库就会有多条重复的数据。但是该数据库(user)并没有指明某个字段是唯一的,所以对于服务端来说,每一次请求都是合理的(即使是相同的参数)。 在没有指明字段值唯一时,服务端添加数据接口,应认为所有请求都是合理的,所以不存在幂等性问题。
假如将 user表中的 user_name
字段设定为不能重复
。那么如何去处理幂等性呢?
user_name
是否存在。user_name
设置唯一索引。推荐1:添加数据入库之前,查询user_name
是否存在。
以上代码可以解决吗?可以,但是必须是建立在单线程,或没有并发的情况。
为什么必须建立在单线程基础上,或没有并发的情况呢?
假设有多个请求同时到达添加用户接口(save),又同时去数据库中查询(user_name)是否存在,如果user_name不存在,则就会同时添加多条数据,造成(user_name)重复。
如何在以上代码上改进解决?在接口方法上加synchronized
或者使用Lock
锁。
这种方案一定不可取,这会导致该接口变成单线程。同时也存在一个的问题,那就是服务器集群环境下,锁只针对当前服务器,如果多个线程分发给多个服务器去处理,那么同样又重复数据。
那么分布式锁可以解决吗?可以的
注意:如果非要用分布式锁解决的话,一定要将锁的粒度细化,用
user_name
作为锁的key。
2:数据表(user)的user_name
设置唯一索引。推荐
代码修改成以下这样
为什么要将user_name
设置唯一索引
在最终数据持久化(数据库)中,能够非常好的保证数据唯一,同时也能在编码中减少许多代码判断。
相信有不少朋友做过完成活动,然后领取积分,红包,啥的,不知道有没有碰到过多次领取,都领取成功的情况。
假设现在有个需求:用户完成任务,领取1
元现金红包
待完成
。领取奖励
。已领取
。现在要实现的是:
先建立表,以下表揭是模拟表
1:用户领取任务,只能领取一次,且重复领取 数据记录表中只能有一条记录
这个实现方式与案例1
一致,需求可以得知 用户id
和 任务id
在用户任务记录表中
是唯一的,所以可以设置一个组合字段的唯一索引。
领取任务的先查询下任务是否领取,然后再添加记录。
2:用户完成任务后,领取奖励,重复领取情况下,只能发放奖励一次
已完成
或者已领取
。已领取
,双重检查。如果不想双重检查可以在接口处获取分布式锁。重要已领取
。重要用户奖励流水表
,并备注因何获得。重要伪代码 就不贴了。
为什么要使用分布式锁?
因为是涉及的金额之类的,如果出错,就会导致财产损失,以后对账都不好对。加了分布式锁之后,就会变成单线程去执行发放奖励,且锁的粒度也小,不会导致整个接口变成单线程。
前端可以控制重复提交吗? 可以
用户在点击
领取奖励按钮
后,在未返回结果的情况下,按钮不可再点击。
前端控制了重复提交,服务端可以不用去控制吗? 不可以
服务端是整个数据添加的入口,前端只是调用服务端接口的一个应用而已。前端控制了,还有其他渠道也可以访问服务端接口。
说明:
此问题我并未有遇到过实际情况,所以不好举例。
因为是修改数据,我都认为每个线程都是合理的,最终数据以最后修改为准。
如何解决:数据表中 每行数据版本
机制
做过三方支付,或者提供接口给别人对接,应该都知道这个幂等性问题。
举个例子:
假如用户支付成功后,就加相应的积分。用三方支付,支付完后,都会被三方进行回调,假如三方回调我们的接口,出错了,或超时了,三方会继续重试,那就会导致多次调用接口。这也是重复提交。
伪代码:假设我们的服务器应该某些原因,没有及时响应给三方调用者,那么就会触发重试,然后又给用户增加了积分。
如何改进与设计
为什么已经处理该订单 也要响应成功
这可以说是幂等性的核心理念吧,多次请求,结果一致,响应结果也是一致。
幂等性就是在对一些唯一的数据,做校验,当服务器多次接收到这些唯一数据时,做合适的处理。但是响应是同样的结果,都是成功。
重点就是在唯一的数据上做处理。