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