PHP:关于PHP商城秒杀防止超卖问题

关于PHP商城秒杀防止超卖问题

序言:

  • 在同样对数据操作的代码下,redis事务比lua脚本还要慢上许多,会偶尔出现1-10单超卖的现象。
  • 如果想要使用redis事务,删减库存的情况,用redis->decr递减库存,不要用程序自带的加减法,这样效果会好一些
  • 推荐使用lua脚本加redis
  • 注意redis事务与mysql的事务不一样,缺少了原子性
  • lua+redis:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。

实现思路:

  1. 在设置秒杀活动的时候,把秒杀商品库存存入redis,在redis里面进行删减库存,秒杀成功在同步到mysql
  2. 秒杀开始,取出redis商品库存,然后让用户进入redis队列,如果队列不存在则创建,队列如果存在,判断用户是否在队列中,如果在队列中则提示以参加过秒杀
  3. 判断redis商品库存是否大于0,如果大于0则秒杀继续,否则提示商品已卖完
  4. 设置好lua脚本,在lua脚本中,再次判商品库存是否大与0,如果是,则库存自动减少1个,因为秒杀商品每人限购1,自动减少成功后返回true,否则返回false
  5. 判断lua脚本返回的状态,如果是true则进行用户队列抢购,如果是false则提示商品已被抢空。
  6. 其中因为用户进入了队列,所以是排队的模式进行抢购下单,这样比较公平,秒杀场景都是一瞬间的事情。
  7. 这六点最作为参考,不作为实际业务场景

一.方案一使用redis事务和watch监听值变化

$goods_total = 20;
    // Redis::set("goods_stock", $goods_total);
    // die;
    // 测试商品秒杀
    $redis_stock = Redis::get("goods_stock");
    if (empty($redis_stock) && $redis_stock == 0) {
    	return "商品已被抢空";
    }
    $user_id = mt_rand(1,999);
    $redis_list = Redis::lRange("user_list",0, -1);
    // 限定只抢购一次
    if (empty($redis_list)) {
        Redis::lPush("user_list", $user_id);
    } else {
        if (in_array($user_id, $redis_list)) {
            return "您已经抢购过啦,用户id:" . $user_id;
        }
        Redis::lPush("user_list", $user_id);
    }
    if ($redis_stock > 0) {
        // 方案1
        Redis::Watch("goods_stock");
        Redis::Multi(); // 开启事务
        Redis::decr("goods_stock");
        $is_ok = Redis::exec();
        if ($is_ok) {
            $user_id = Redis::rPop("user_list");
            DB::beginTransaction();
            try {
                $data = [
                    "user_id"   => $user_id,
                    "orders_num" => time() . mt_rand(10, 999),
                ];
                $res = DB::table("test_table")->lockForUpdate()->insert($data);
                echo "抢购成功,用户id:" . $user_id;
                DB::commit();
                return;
            } catch (\Exception $e) {
                DB::rollBack();
                Redis::Discard();
                echo "抢购失败,用户id:" . $user_id . "," . $e->getMessage();
                return;
            }
        } else {
            echo "商品已被抢空,用户id:" . $user_id;
            Redis::Discard();
            return;
        }
    }
    echo "商品已被抢空,用户id:" . $user_id;
    return;

二.方案二使用lua脚本+redis

$goods_total = 20;
    // Redis::set("goods_stock", $goods_total);
    // die;
    // 测试商品秒杀
    $redis_stock = Redis::get("goods_stock");
    if (empty($redis_stock) && $redis_stock == 0) {
    	return "商品已被抢空";
    }
    $user_id = mt_rand(1,999);
    $redis_list = Redis::lRange("user_list",0, -1);
    // 限定只抢购一次
    if (empty($redis_list)) {
        Redis::lPush("user_list", $user_id);
    } else {
        if (in_array($user_id, $redis_list)) {
            return "您已经抢购过啦,用户id:" . $user_id;
        }
        Redis::lPush("user_list", $user_id);
    }
    if ($redis_stock > 0) {
        // 方案2
        // lua脚本
        $str = <<<Lua
        local key   = KEYS[1];
        local redis_stock = redis.call('get', key);
        if (tonumber(redis_stock) > 0)
        then
            redis.call('decr', key);
            return true;
        else
            return false;
        end
        Lua;
        $res = Redis::eval($str, 1, "goods_stock");
        if ($res) {
            $user_id = Redis::rPop("user_list");
            DB::beginTransaction();
            try {
                $data = [
                    "user_id"   => $user_id,
                    "orders_num" => time() . mt_rand(10, 999),
                ];
                $res = DB::table("test_table")->lockForUpdate()->insert($data);
                echo "抢购成功,用户id:" . $user_id;
                DB::commit();
                return;
            } catch (\Exception $e) {
                DB::rollBack();
                // Redis::Discard();
                echo "抢购失败,用户id:" . $user_id . "," . $e->getMessage();
                return;
            }
        } else {
            echo "商品已被抢空,用户id:" . $user_id;
            // Redis::Discard();
            return;
        }
    }
    echo "商品已被抢空,用户id:" . $user_id;
    return;
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值