幂等和重复请求解决方案

493 阅读4分钟

什么是幂等性

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

通俗的讲:

拿Web应用的接口来说明就是一个请求提交一次和提交多次对系统产生的影响是相同的,则这个接口就具有幂等性,是幂等接口

关键:多次执行和一次执行效果相同

比如:

一个系统的用户注册接口,

  • 若不是幂等接口,则同一个注册请求提交多次,系统会创建多个用户
  • 若是幂等接口,则同一个注册请求提交多次,系统最终只会创建一个用户

为什么要幂等

我们可能会遇到这些情况:

  1. 用户在前台重复点击了一个按钮,导致请求提交了多次
  2. 黑客劫持了请求,然后不断重放请求服务端
  3. 因网络原因调用方收不到服务端的响应结果,触发了超时重试,导致请求提交了多次
  4. 在消息队列应用场景中,一个消息因某些原因消费了多次,即重复消费问题
  5. ......

在这些场景下,如果系统的接口或处理逻辑不是幂等可能会给系统带来意想不到的问题
总之,幂等就是防止重试或重复执行出现问题

如何实现幂等

保证幂等的方案有:

  1. 数据库唯一索引,主要应对新增数据的场景,保证只会新增一条数据

  2. 乐观锁,主要应对更新数据库数据的场景,保证只会执行一次更新

  3. token机制

    大致方案:

    1. 客户端发送请求前,先向服务端申请token
    2. 服务端会生成token,并存到redis中,然后返回给客户端
    3. 客户端携带token进行请求
    4. 服务端收到请求,会尝试删除请求对应的token(即改变服务端的状态),若删除成功则是第一次请求;删除失败则是重复请求,不做处理

    该方案的思路是:给请求分配一个唯一标识(如token),然后在第一次处理请求时留痕(如:删除token),后续请求就能判断是重复请求了

  4. 锁+先查再更新。某些更新操作,可以先查询再尝试更新(加锁可保证原子性,避免并发出问题),比如:要新增一条数据,可以先查询数据是否存在,存在则不新增。

    分布式环境下要用分布式锁

  5. 状态机幂等

如何处理重复请求

要判断当前请求是否已经处理过了关键在于:第一次请求处理时要留痕,用于后续判断

方案有:

  1. 唯一编号去重,请求具有唯一编号,根据编号判断是否是重复请求
  2. 根据请求参数去重

具体讲下方案2:

可行性分析:

同一个请求,无论请求多少次,携带的请求参数肯定是一样的,所以可以根据请求参数判断是否是重复请求

具体方案:

将请求参数拼接成一个字符串(假设叫做key),使用一个集合来保存处理过的请求对应的key,每次处理请求时都判断集合中是否已存在对应的key,存在则是重复请求;不存在则是第一次请求,并保存key。

关键:

第一次请求处理时要留痕,用于后续判断。

优化点:

  1. 请求参数拼接成的字符串可能很长,可以生成摘要再存储。摘要是定长的,且短。
  2. key可以设置过期时间,防止相似请求一直不能被处理。
  3. 使用redis来存储。优点:分布式存储、快

实战:

将请求参数使用MD5生成摘要作为key,存储到redis中,并且设置过期时间。

应该使用setnx+expire,若成功,代表是第一次请求;若失败,则代表是重复请求。

为什么不使用get判断key是否存在,不存在再set保存key?并发会出问题,多个线程可能判断都不存在,还是会导致请求被处理多次,必须保证get+set的原子性,而setnx+expire就是原子操作。

总结

有 重试 或 重复执行 的场景一定需要注意幂等问题
实现幂等的关键是:留痕+同步校验,第一次留痕,后续判断是否有痕。