转载于:https://blog.csdn.net/weixin_43356354/article/details/122688020

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

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

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

 

一.方案一使用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;

  

版权声明:本文为诗里刻画的影子原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zinging/p/16481098.html