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