幂等的意思就是保存数据库如果是主键冲突,则返回数据库里面已经存在的数据
幂等一直都是一个业务接口需要保证的特性,同一笔请求,无论处理多少次,最后的结果都是要想一致。
一、幂等控制
定义:在某个函数或某个接口使用相同参数调用一次或无限次后,生成的结果是一样的不会重复执行,在实际应用中一般针对于接口进行幂等性设计。
1.常用场景
- 前端数据重复提交(比如同时多次点击保存造成重复提交)
- 订单支付请求重复、用户重复下单(无论是网络超时、系统bug还是其他原因,都应该只扣一次钱)
- Mpush推送消息、系统短信(及时系统异常造成同样的消息后端推送(发送)多条,也应该限制用户最后只收到一条)
2.保障幂等性策略
(1)保证幂等策略
幂等需要通过唯一的code值(不一定是code,总之是数据表中的唯一值)来保证,当后端发现是相同的值传过来,就会认为是同一笔业务从而不会重复多次的进行提交或保存之类的操作。使用这个唯一的值来确保,后面多次的相同的值的处理逻辑和执行效果是一致的。
下面以支付为例,在不考虑并发的情况下,实现幂等很简单:
①先查询一下订单是否已经支付过;
②如果已经支付过,则返回支付成功;如果没有支付,进行支付流程,修改订单状态为‘已支付’。
(2)防重复提交策略
上述的保证幂等方案是分成两步的,第②步依赖第①步的查询结果,无法保证原子性。
在高并发下就会出现第二次请求在第一次请求第②步订单状态还没有修改为‘已支付状态’的情况下到来。
解决方案:
把查询和变更状态操作加锁,将并行操作改为串行操作。
3.幂等技术
- 查询操作:在数据不变的情况下查询一次和查询多次,查询结果是一样的,select是天然的幂等操作。
- 删除操作:删除一次和多次删除都是把数据删除,删除也是幂等操作。(返回结果可能不一样,删除的数据不存在,返回0;删除的数据多条,返回多个结果)
- 唯一索引:数据库创建唯一索引或组合唯一索引,都可以避免脏数据,保证幂等,但是注意处理异常(多余的数据如何处理,可在系分时与开发确认逻辑,合理情况下在测试时注意校验)
- 悲观锁:即每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁(在做操作之前先将数据上锁),这样别人想拿这个数据就会block直到它拿到锁,悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。悲观锁获取数据用的不多,主要使用不读容易出现锁表情况。如:select * from table_xxx where id='xxx' for update;
①id字段一定是主键或者唯一索引,不然是锁表;
②悲观锁一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用;
- 乐观锁:类似于CAS,也是compareAndSet操作,在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高,常用的就是加版本号控制,或者根据唯一条件。如:update t_user set name='123',ver=ver+1 where ver='1.2.3'。
- 分布式锁:分布式系统构建唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统,在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多个系统,也就是分布式系统中得解决思路。在一些小型系统或者并发不高的项目里面,防止重复执行,可以采用select+insert;
- API层面的幂等:API层面的幂等,例如提交数据,如何控制重复提交,这里可以在提交数据的form表单或者客户端软件,增加一个唯一标示,然后服务端,根据这个UUID来进行去重,这样就能比较好的做到API层面的唯一标示。
- 状态机:在设计单据相关的业务或是任务相关的业务,肯定会涉及到状态机,就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。
4.幂等性校验
接口(方法)重复调用:请求数据不变的情况下A多次调用B接口(方法):
①首次调用产生结果、效果符合预期;
②再次调用,返回约定提示关键词,并关注是否结束流程终止本次执行;
表单重复提交:通常出现在新建、修改、下单等功能,多次提交产生多条记录:
①首次提交产生结果、效果符合预期;
②再次提交,是否具有防抖动处理,且给出友好交互;
③关注是否发出多条请求;
数据重复操作:多为insert、update操作,主要关注某一个或多个流程产生或影响的数据是否唯一;