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
上一篇:容器


下一篇:Mybatis的注解方式,一次性注入多个参数需要用@Param