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

Redis优化|高并发防重 处理解决Redis用户请求重复的有效方法,redis如何应对用户重复请求

Redis优化 | 高并发防重:应对用户重复请求的实战方案

最新动态(2025年8月):随着电商大促季临近,某头部平台因Redis缓存击穿导致重复下单问题,单日损失超千万,技术团队紧急采用"Lua脚本+令牌桶"组合方案,成功将重复请求率降至0.02%以下。


为什么重复请求是"隐形杀手"?

想象这个场景:用户疯狂点击提交订单按钮,由于网络延迟,前端没能立即禁用按钮,此时如果后端没有防重机制,可能导致:

  • 重复扣款
  • 超卖库存
  • 垃圾数据堆积

典型case:2025年某社交平台红包活动中,因未做Redis防重,出现用户通过脚本重复抢包,单个用户最高领取红包达1,237次。


Redis防重的4大核心方案

方案1:唯一键锁定(适合写操作)

# Python示例:SETNX实现分布式锁
def handle_order(user_id, order_id):
    lock_key = f"order_lock:{user_id}:{order_id}"
    # 设置10秒过期防止死锁
    if redis.setnx(lock_key, 1):  
        redis.expire(lock_key, 10)
        try:
            # 真实业务处理
            create_order()
            return True
        finally:
            redis.delete(lock_key)
    else:
        raise Exception("操作过于频繁,请稍后再试")

适用场景:订单创建、支付等关键操作

Redis优化|高并发防重 处理解决Redis用户请求重复的有效方法,redis如何应对用户重复请求


方案2:请求指纹去重(适合读操作)

// Java示例:利用MD5生成请求指纹
String requestFingerprint = DigestUtils.md5Hex(
    userId + "_" + 
    URLEncoder.encode(apiParams) + "_" + 
    System.currentTimeMillis()/1000 // 时间窗口
);
if(redis.sAdd("req_fingerprints", requestFingerprint) == 0){
    return "请勿重复提交";
}
redis.expire("req_fingerprints", 30); // 30秒有效期

优化技巧:可结合布隆过滤器减少内存消耗


方案3:令牌桶限流(全场景适用)

-- Redis Lua脚本实现令牌桶
local tokens_key = KEYS[1]
local timestamp = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local capacity = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
    last_tokens = capacity
end
local new_tokens = math.min(
    capacity, 
    last_tokens + (timestamp - (last_tokens == capacity and timestamp or tonumber(redis.call("get", tokens_key..":ts"))) * rate
)
if new_tokens < requested then
    return 0
end
redis.call("setex", tokens_key, ttl, new_tokens - requested)
redis.call("setex", tokens_key..":ts", ttl, timestamp)
return 1

参数建议

  • 普通接口:1000请求/分钟
  • 核心接口:300请求/分钟

方案4:双重校验删除(防时序问题)

// Go示例:先删后查防并发
func Dedup(key string, ttl int) bool {
    if redis.Exists(key) {
        return false
    }
    redis.Setex(key, ttl, "1")
    // 二次校验防止极端情况
    if redis.Incr(key) > 1 { 
        redis.Del(key)
        return false
    }
    return true
}

实战避坑指南

  1. 时间差陷阱
    不要依赖客户端时间,所有时间戳用Redis的TIME命令获取

  2. 雪崩预防
    对防重key设置随机过期时间:TTL = base_time + random(0, 3000ms)

  3. 集群环境注意
    使用RedLock算法时,至少需要3个主节点才能保证可靠性

    Redis优化|高并发防重 处理解决Redis用户请求重复的有效方法,redis如何应对用户重复请求

  4. 监控指标

    • 重复请求拦截率
    • Redis防重key内存增长趋势
    • Lua脚本执行耗时

性能压测数据对比

某金融系统在引入组合方案后的测试结果(单Redis节点):

方案 QPS上限 内存消耗 误判率
纯SETNX 12,000 0%
指纹+布隆过滤器 85,000 1%
Lua令牌桶 53,000 0%
混合方案 68,000 01%

2025年的最佳实践表明:混合使用Lua脚本的令牌桶限流+请求指纹去重,能在保证系统吞吐量的同时,将重复请求控制在万分之一以下,关键点在于:

  1. 读多写少场景优先用指纹方案
  2. 资金相关操作必须加分布式锁
  3. 永远要给防重key设置过期时间
  4. 大促前用redis-benchmark做专项测试

防重不是简单的技术问题,而是直接影响用户体验和公司营收的关键防线。

发表评论