• Redis 秒杀功能:不带锁的,只是个基础功能,后续学习带锁的秒杀


    Redis - 秒杀功能:不带锁的

    1、秒杀功能:有限的商品,大量的用户同时抢购,主要功能难点:高并发

    2、redis实现原理:使用redis链表,进行pop操作,因为pop操作是原子性的,即使同时有大量用户同时请求,也是依次执行

    3、准备工作:
    1)提前将商品ID写入数据库
    2)设置定时任务,开始抢购时设置链表超时时间(可以不要这一步)

    4、秒杀操作:

    1)判断商品库存是否大于0
    2)判断用户是否已经秒杀过商品
    3)购买操作(库存减1是链表自动的操作,无需手动减1)
    4)记录用户购买记录信息
    

    具体代码如下所示,其中php操作redis的类下一篇随笔记录一下:

    <?php
    /**
     * Created by PhpStorm.
     * User: wkk
     * Time: 2021/12/5 - 15:19
     * Desc: <redis实践-秒杀商品>
     */
    include './redis.php';
    
    // 1、秒杀功能:有限的商品,大量的用户同时抢购,主要功能难点:高并发
    
    // 2、redis实现原理:使用redis链表,进行pop操作,因为pop操作是原子性的,即使同时有大量用户同时请求,也是依次执行
    
    // 3、抢到商品后,需要记录到用户购买表,记录userid已抢到商品id
    
    class bugGoods
    {
        // 定义存放商品的key信息
        private static $key = '2021_12_05_goods_list';
    
        // 用户抢购记录key
        private static $userLogKey = '2021_12_05_user_list';
    
        // 定义商品个数1000个
        private static $goodsNum = 1000;
    
        // 定义商品过期时间,单位秒
        private static $expire = 3600;
    
        /**
         * 1、提前准备工作:将商品写入redis的list中
         */
        public function addGoodsIntoList()
        {
            $redis = new WkkRedis();
    
            // 将商品ID写入List中
            for ($i = 1; $i <= self::$goodsNum; $i++) {
                echo "商品{$i}已写入库存 \n";
                $redis->lpush(self::$key, $i);
            }
    
            return $redis->gelListLen(self::$key);
        }
    
        /**
         * 2、设置商品的List有效时间
         */
        public function setExpire(): bool
        {
            $redis = new WkkRedis();
            return $redis->setExpire(self::$key, self::$expire);
        }
    
        /**
         * 3、获取商品库存
         */
        public function getGoodsNum(): int
        {
            $redis = new WkkRedis();
            return (int)$redis->gelListLen(self::$key);
        }
    
        /**
         * 4、购买商品操作,从链表中头部pop取出商品ID
         */
        public function buy()
        {
            // 获取商品库存数量
            $goodsNum = $this->getGoodsNum();
            // 商品库存数为0
            if ($goodsNum <= 0) {
                return 0;
            }
    
            $redis = new WkkRedis();
            return $redis->lpop(self::$key);
        }
    
        /**
         * 判断用户是否已经抢到,抢到则不允许再次提交
         *
         * @param $userId
         * @return bool
         */
        public function checkUserBuy($userId): bool
        {
            $redis = new WkkRedis();
            return $redis->sismenber(self::$userLogKey, $userId);
        }
    
        /**
         * 添加抢到商品的用户
         *
         * @param $userId
         * @return bool|int
         */
        public function addUserBuyLog($userId)
        {
            $redis = new WkkRedis();
    
            // 将用户添加到set中,集合(不能重复)
            return $redis->saddValue(self::$userLogKey, $userId);
        }
    
        /**
         * 核心逻辑:秒杀操作
         */
        public function secKill($userId): bool
        {
            if (!$userId) {
                echo "用户ID为空,秒杀失败\n";
                return false;
            }
    
            // 判断商品库存是否大于0
            $goodsStock = $this->getGoodsNum();
            if ($goodsStock <= 0) {
                echo "商品库存数为0,秒杀失败\n";
                return false;
            }
    
            // 判断用户是否已经秒杀过商品
            $isBought = $this->checkUserBuy($userId);
            if ($isBought) {
                echo "用户【{$userId}】已经购买过,秒杀失败\n";
                return false;
            }
    
            // 购买操作(库存减1是链表自动的操作,无需手动减1)
            $buy = $this->buy();
            if (!$buy) {
                echo "购买失败,lpop操作失败,秒杀失败\n";
                return false;
            }
    
            // 写入购买记录表
            $this->addUserBuyLog($userId);
            echo "用户【{$userId}】秒杀成功!\n";
            $leftNum = $goodsStock - 1;
            echo "还剩商品个数:{$leftNum}\n";
            return true;
        }
    }
    
    $obj = new bugGoods();
    // 添加商品到库存操作
    // $obj->addGoodsIntoList();
    
    // 购买操作,pop取值操作
    $userIds = [
        1, 1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 8, 9, 10
    ];
    foreach ($userIds as $userId) {
        sleep(2);
        $obj->secKill($userId);
    }
    
    // 执行结果如下:
    用户【1】秒杀成功!
    还剩商品个数:992
    用户【1】已经购买过,秒杀失败
    用户【2】秒杀成功!
    还剩商品个数:991
    用户【3】秒杀成功!
    还剩商品个数:990
    用户【4】秒杀成功!
    还剩商品个数:989
    用户【5】秒杀成功!
    还剩商品个数:988
    用户【5】已经购买过,秒杀失败
    用户【6】秒杀成功!
    还剩商品个数:987
    用户【7】秒杀成功!
    还剩商品个数:986
    用户【8】秒杀成功!
    还剩商品个数:985
    用户【8】已经购买过,秒杀失败
    用户【8】已经购买过,秒杀失败
    用户【9】秒杀成功!
    还剩商品个数:984
    用户【10】秒杀成功!
    还剩商品个数:983
    

    本文来自博客园,作者:alisleepy,转载请注明原文链接:https://www.cnblogs.com/alisleepy/p/15647547.html

  • 相关阅读:
    python学习笔记6基本对象和流程语句整理
    暑假第二周总结(2018.7.16——7.22)
    暑假第三周总结(2018.7.23——7.29)
    《大道至简》读后感
    暑假第四周总结(2018.7.30——8.5)
    暑假第一周总结(2018.7.97.15)
    .NET中实现页面间的参数传递 QueryString\Application\Session\Cookie(转载)
    px和dip的区别与转换公式
    android开发ListView+Json+异步网络图片加载+滚动翻页的例子(图片能缓存,图片不错乱)
    正则表达式的惰性匹配方法
  • 原文地址:https://www.cnblogs.com/alisleepy/p/15647547.html
Copyright © 2020-2023  润新知