当前位置:首页 > 问答 > 正文

怎么避免Redis被重复请求整得乱七八糟,实用解决办法分享

怎么避免Redis被重复请求整得乱七八糟,实用解决办法分享

你有没有遇到过这种情况:一个热门商品开卖,瞬间涌进来无数请求,都抢着要扣减库存,或者,用户手速太快,连续点击了多次提交订单按钮,结果呢?Redis里的库存可能被扣成了负数,或者同一个订单生成了好几遍,数据库压力山大,数据也变得一团糟,这就是典型的重复请求导致的问题,在高并发场景下尤其常见,今天我们就来聊聊几个非常实用的解决办法,帮你把Redis管理得井井有条。

第一招:加把“锁”,让请求排好队(来源:分布式锁的常见实现思路)

这是最直接能想到的办法,核心思想就是,当一个请求要来操作某个关键数据(比如商品A的库存)时,先在Redis里创建一个“锁”,这个锁其实就是一个特殊的Key-Value对。

  • 具体怎么做?
    1. 请求进来后,先检查Redis里是否存在一个特定的锁Key,lock:product_123
    2. 如果不存在,说明当前没有其他请求在操作这个商品,那么当前请求就用自己的唯一标识(比如UUID)作为Value,去设置这个锁Key,这里有个关键点:设置锁的时候一定要加上过期时间!SET lock:product_123 request_uuid NX PX 10000NX表示只有Key不存在时才能设置成功,PX 10000表示这个Key在10秒后自动过期,设置过期时间是必须的,能防止某个请求拿到锁后因为程序崩溃等原因无法释放锁,导致其他请求永远等待(死锁)。
    3. 如果设置成功了,恭喜,这个请求就拿到了锁,可以安心地去执行扣减库存等后续操作了。
    4. 操作完成后,一定要记得释放锁,释放锁时最好验证一下Value是不是自己当初设置的那个UUID,确保不会误删其他请求的锁(比如自己的操作比较慢,锁快过期了才完成,此时可能已经有其他请求尝试获取锁了),可以用Lua脚本保证验证和删除的原子性。
    5. 如果第一步检查时锁已经存在,说明有其他请求正在操作,那么当前请求可以选择等待一小会儿再重试,或者直接返回“操作频繁,请稍后再试”的提示给用户。

这种方法相当于让并发的请求变成了“排队”处理,同一时间只有一个请求能操作数据,自然就避免了混乱,但要注意锁的粒度(是锁整个库存服务还是某个具体商品?)和过期时间的设置,设置太短可能导致锁失效,太长又影响性能。

第二招:让Redis自己“原子”操作,拒绝插队(来源:Redis原子操作的应用)

加分布式锁虽然有效,但稍微有点重,有时候我们可不可以不加锁,也能保证安全呢?答案是可以的,那就是利用Redis自身提供的原子操作。

Redis是单线程执行命令的,它的每个命令本身都是原子性的,也就是说,一个命令在执行过程中不会被其他命令打断,我们可以利用这个特性,把一系列操作写成一个原子性的指令。

  • 具体怎么做?
    1. 使用原子命令:INCR(自增)、DECR(自减)这样的命令本身就是原子的,对于库存扣减,我们可以先判断库存是否大于0,然后使用 DECR 命令,但更严谨的做法是使用 Lua脚本
    2. 使用Lua脚本: Lua脚本在Redis中执行时,可以保证脚本内的所有命令被当作一个整体、按顺序执行,期间不会被其他命令插入,这简直是解决复杂原子操作的利器,我们可以写一个Lua脚本,里面包含检查库存、如果库存大于0则扣减、否则返回0的逻辑,这样,即使有1000个请求同时发送这个脚本给Redis,Redis也会一个一个地执行,执行脚本的过程中数据状态不会被干扰。

使用原子操作,特别是Lua脚本,性能比分布式锁更高,因为它减少了网络通信和锁竞争的开销,对于库存扣减、秒杀扣款这类需要“判断+操作”的场景,这是首选方案。

第三招:给请求盖个“戳”,识别重复分子(来源:利用幂等性防重复)

前面两招主要解决的是并发时操作顺序的问题,但有些请求本身就是用户重复提交的,比如连续点击,我们希望在业务层面就把它拦下来,这时候,幂等性设计就派上用场了,幂等性就是说,同一个请求你执行一次和执行多次,产生的结果是一样的。

  • 具体怎么做?
    1. 生成唯一标识: 在客户端(比如网页或APP)发起一个可能重复的请求(如提交订单)时,由前端或后端生成一个唯一的令牌(Token),比如叫 requestId,这个Token可以和服务器的Session关联,或者更好的是随着请求一起发到后端。
    2. 利用Redis做校验: 在请求到达核心业务逻辑之前,先检查Redis中是否存在这个Token。
      • 如果不存在,说明是第一次请求,那么放行,并在Redis中设置这个Token,同时设置一个合理的过期时间(比如5分钟)。
      • 如果已经存在,说明这个请求之前已经处理过了,直接返回上一次的处理结果(订单提交成功”的提示),而不再执行真正的扣款、生成订单等操作。
    3. 这样,即使用户点了十次提交按钮,也只有第一次请求会真正生效,后面的请求都会被Redis识别为重复而拦截。

这种方法直接从源头避免了重复请求执行核心逻辑,对数据库和业务代码的压力最小,特别适合表单提交、支付发起等场景。

总结一下

让Redis不被重复请求搞乱,没有一刀切的最优解,关键是根据你的业务场景选择合适的方法,或者组合使用:

  • 分布式锁 像交通警察,让请求排队,适合跨服务、操作复杂的临界资源控制。
  • 原子操作 像一道坚固的闸门,利用Redis自身特性保证操作不被打断,性能高,适合简单的计数、判断类场景。
  • 幂等性设计 像一张唯一的门票,从源头识别并过滤掉重复请求,适合前端交互频繁、需要防止用户重复提交的场景。

结合实际情況,灵活运用这些方法,你的Redis就能在高并发的冲击下依然保持从容不迫了。

怎么避免Redis被重复请求整得乱七八糟,实用解决办法分享