使用redis防止抢购商品超卖

本文探讨使用Redis解决抢购商品超卖问题。通过对比list和incrby方法,阐述原子性操作的重要性。压力测试验证incrby的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

  • redis不仅仅是单纯的缓存,它还有一些特殊的功能,在一些特殊场景上很好用。
  • 本篇博文用来测试下使用redis来防止抢购商品超卖问题。

内容:

  • 使用redis的list进行测试

    思路是设置一个redis列表List,假设有十个商品,每次请求先判断List的长度,小于十就能抢到商品,将用户信息存放到List中。代码如下

 //进行抢购
    protected function way_list(){
        $num = $this->redis->lLen();
        if($this->redis->lLen()>=self::AMOUNTLIMIT){
            $this->writeLog("抢购失败".$num);
            return;
        }else{
            $this->redis->rPush($num);
            $this->writeLog("抢购成功".$num);
        }
    }

结果:失败!

可以很明显数量不对顺序也不对。

分析了下原因,在代码执行时,多用户并发请求时,第一个用户判断List长度符合条件还未进行List写入时,第二个用户也通过了List长度判断。所以就导致执行失败。

这就没有利用到redis的原子性

所以进行了改良

  • 使用redis 的incrby。incrby将制定key 的值增加指定的增量,并返回增量后的值。是一个原子性操作。所谓的原子性操作就是执行该方法后要嘛成功要嘛失败。

思路就是设置一个键值对存放被抢购数量,每次一个用户进来就将该值加一进行判断,如果小于抢购的商品数量则抢购成功,否则失败。代码如下

 protected function way_string(){
        //判断是否有初始化
        if(!$this->redis->exists(self::sold_name)){
            $this->redis->setnx(self::sold_name,0);
        }
        if($this->redis->incrby(self::sold_name,1) > self::AMOUNTLIMIT){
            $this->writeLog('失败');
        }else{
            $this->writeLog('成功');
        }
    }

结果

压力测试了几次都没有出现问题

通过apache自带的ab压力测试,进行五百次连接请求,并发三百次,没有出现超卖行为。

总结:会出现超卖主要是由于用户在请求的时候,代码在执行是有先后,会导致执行结果不符合预期。而采用redis的原子性就能避免。

转载于:https://www.cnblogs.com/lsrd/p/10241141.html

<think>好的,我现在需要回答用户关于Redis如何防止问题。首先,我得回忆一下Redis的相关机制,尤其是和库存控制相关的部分。 用户提到的是“防止的机制或实现方法”,所以我要集中在这方面。通常发生在高并发场景下,多个请求同扣减库存,导致库存变为负数。Redis作为内存数据库,处理高并发很有优势,但需要正确的机制来保证原子性。 首先想到的是Redis的原子操作,比如DECR或者INCR。这些命令是原子执行的,可以确保库存扣减不会被其他操作打断。例如,当库存为1,多个DECR操作会依次执行,避免。不过,这可能还不够,因为在分布式环境中,还需要考虑其他因素,比如网络延迟或重试。 然后,Lua脚本应该也是关键点。Redis支持执行Lua脚本,这些脚本在服务器端原子执行。使用Lua脚本可以将多个操作(比如检查库存和扣减)合并成一个原子操作,避免中间状态被干扰。比如,先判断库存是否大于0,然后再执行DECR,这样就能确保不会。用户提供的引用里也提到了Redis Lua脚本的重要性,特别是在处理库存扣减,所以需要重点说明这一点。 接下来,分布式锁可能也是一个方法,比如Redlock算法。当多个实例需要协调,分布式锁可以确保同一间只有一个客户端能执行扣减操作。不过,分布式锁可能会引入性能开销,所以可能不是最优解,但在某些复杂场景下可能有必要。引用中的[2]和[3]提到了Redlock和分布式锁的应用,需要适当引用。 另外,事务(multi/exec)可能被提到,但要注意Redis的事务并不是严格的原子性,它只是将命令顺序执行,中间不会插入其他命令,但如果其中一个命令失败,其他命令仍会执行。所以可能不如Lua脚本可靠。 还有乐观锁,使用WATCH命令监控库存键,然后在事务中执行操作。如果键被修改,事务会失败,需要重试。这可能在高并发下导致重试次数增多,影响性能,但也是一个可行方案。 然后需要考虑库存预扣,比如秒杀场景中,用户下单预扣库存,支付成功后再实际扣减,否则恢复库存。这需要结合其他机制,比如设置预扣库存的过期间,避免占用太久。 在组织回答,需要分点说明不同的机制,并指出最佳实践通常是使用Lua脚本,因为原子性高,性能好。同引用用户提供的资料中的内容,比如引用[1]提到的Lua脚本,引用[2]中的Redlock,以及引用[3]中的分布式锁对比,来支持不同方法的适用场景。 可能还需要提到其他辅助措施,比如限流,防止过多的请求压垮系统;库存缓存预热,避免大量请求直接访问数据库;以及监控和告警,及发现异常情况。 最后,生成相关问题要围绕Redis的库存控制、Lua脚本、分布式锁、性能优化等方面展开,帮助用户深入理解。</think>### Redis 防止的机制与最佳实践 在秒杀、抢购等高并发场景中,**问题**(库存扣减后变为负数)是核心挑战之一。Redis 通过以下机制实现高效的库存控制: --- #### 1. **原子操作:单命令原子性** Redis 的 `DECR`、`INCR` 等命令是原子执行的,适合简单库存扣减。例如: ```bash # 初始化库存 SET stock:item001 100 # 扣减库存(原子操作) DECR stock:item001 ``` 若库存充足则返回扣减后的值,若不足则返回负数,需在业务层判断并拦截后续操作[^1]。 --- #### 2. **Lua 脚本:组合原子操作** 复杂逻辑(如“检查库存→扣减→记录订单”)需使用 **Lua 脚本**,确保多命令原子执行: ```lua local stock = tonumber(redis.call('GET', KEYS[1])) if stock > 0 then redis.call('DECR', KEYS[1]) return 1 -- 扣减成功 else return 0 -- 库存不足 end ``` 通过 `EVAL` 执行脚本,避免并发导致的数据竞争。 --- #### 3. **分布式锁:协调多实例操作** 若业务涉及多步骤操作(如支付后扣减),需用分布式锁(如 **Redlock 算法**)保证串行化: ```lua -- 尝试加锁 local lock = redis.call('SET', 'lock:item001', '1', 'NX', 'PX', 10000) if lock then -- 执行业务逻辑 -- ... redis.call('DEL', 'lock:item001') end ``` 锁需设置间,避免死锁[^2][^3]。 --- #### 4. **预扣库存与异步处理** 在高并发场景中,可结合 **预扣库存** 与消息队列: 1. 用户下单预扣库存(Redis 扣减) 2. 支付成功后实际扣减 3. 支付后通过定任务恢复库存 --- #### 5. **限流与熔断** 通过 Redis 的计数器或令牌桶算法限制请求速率: ```lua -- 每秒限流 1000 次 local key = 'rate_limit:item001' local limit = 1000 local current = redis.call('INCR', key) if current == 1 then redis.call('EXPIRE', key, 1) end if current > limit then return 0 -- 限流 end ``` --- #### 最佳实践总结 | 场景 | 推荐方案 | 优势 | |------|----------|------| | 简单扣减 | 原子命令(`DECR`) | 性能高 | | 复杂逻辑 | Lua 脚本 | 原子性+灵活性 | | 分布式环境 | Redlock 锁 | 容错性强 | | 秒杀峰值 | 预扣库存+限流 | 防雪崩 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值