通常说重复请求,指的是同一个业务含义的动作发生了两次或多次。分为两种类别:

  1. 串型重复请求:请求是分先后的,例如:程序处理异常引发的自动重试;
  2. 并行重复请求:多次请求同时发生,例如:
    a) 同一个动作,用户顺手短时间内操作了多次;
    b) 多端同时操作,比如两台电脑同时修改了一个单子的状态;

场景的不同,处理的方式也就不同。
场景一:串型重复请求
串型重复请求最典型的发生场景就是调用接口异常了,然后接口自动重试。由于前后两次请求存在先后顺序,所以只要做好接口幂等就可以比较完善的解决,无需太多针对此种重试的特别设计。

场景二:并行重复请求
并行的重复请求同时发生的特性,必须通过针对性的设计规避。通常的做法:
a) 前端用户交互设计规避用户连续点击。比如:点击后按钮立即置位不可操作状态;
b) 后端同步分布式锁进行控制。常用的做法:
i. 方式一:增加单独的防重表,通过唯一索引控制。处理完成后再删除数据;
ii. 方式二:利用redis的setnx命令,实现分布式锁控制。处理完成后再删除换成数据;
高并发场景下采用缓存控制的方案显然会更好些。那此设计会不会有什么限制或衍生问题呢?必然有的:
问题一:利用缓存实现分布式锁的前提必须有能唯一识别的key, 那如果某种场景下业务数据里无法抽取出这种key怎么办呢?
问题二:处理完成删除key,如果删除失败了呢?岂不是被长时间锁住无法操作?
问题一的解决方式通常是给前端提前内置一个唯一标识。例如订单下单前,进入订单页面的时候可以由后端生成一个唯一标识给前端页面。这样前端请求的时候就可以带上此标识,后端根据此标识进行控制。当然一些特定场景,也可以用用户ping进行控制,限制一个用户当前操作在几秒内只能操作一次。用用户ping的好处还有可以用于多端控制。
问题二的流行解决方式就是过期删除。删除的方式又分为主动删除和惰性删除。主动删除是处理完成后删除缓存key,惰性删除则是通过定期+惰性的方式。后台线程定期自动清理,如果重复请求在定期清理之前。则判断可以设置的时间是否已经过期,如果过期则重新设置。