php+redis实现秒杀

php+redis实现秒杀,当中有lua脚本

risenarrow
2023-05-18 21:15

秒杀的步骤

1.设置库存

设置库存直接采用redis set 命令进行模拟


//设置 key 为 SP1001ID 的商品库存为10

set SP1001ID 10

1

2

2用户参与秒杀

用户参与秒杀本质上要做的事情就是,1.记录秒杀成功用户信息;2.商品库存减1;

涉及到的问题:


1.redis链接超时问题

解决方案:设置redis连接池


PHP设置连接池:https://www.cnblogs.com/daizhongxing/p/13840211.html

注意:PHP为脚本语言,脚本执行完毕Redis 连接自动关闭,所以连接池并不能跨脚本使用。

使用连接池的原因:在高并发情况下,频繁地创建和释放 Redis 连接会对性能有较大影响。

连接池的原理:预先创建多个 Redis 连接,在进行 Redis 操作时直接获取已经创建的连接进行操作,操作完成后不会释放,后续其他 Redis 操作可以继续使用。这样就避免了频繁的 Redis 连接和释放。




class RedisFunction

{


    private static $connections = array(); //定义一个对象池

    private static $servers = array(); //定义redis配置文件


    public static function addServer($conf) //定义添加redis配置方法

    {

        foreach ($conf as $alias => $data){

            self::$servers[$alias]=$data;

        }

    }


    public static function getRedis($alias,$select = 0)//两个参数要连接的服务器KEY,要选择的库

    {

        if(!array_key_exists($alias,self::$connections)){  //判断连接池中是否存在

            $redis = new Redis();

            $redis->connect(self::$servers[$alias][0],self::$servers[$alias][1]);

            self::$connections[$alias]=$redis;

            if(isset(self::$servers[$alias][2]) && self::$servers[$alias][2]!=""){

                self::$connections[$alias]->auth(self::$servers[$alias][2]);

            }

        }

        self::$connections[$alias]->select($select);

        return self::$connections[$alias];

    }

}


2.超卖问题

解决方案为:超卖问题 —— 使用Redis的乐观锁机制 也就是设置一个watch事件去监听,然后开始Redis的事务


//对key进行监听

$redis->watch("SP1001ID");

if(!$goodsCount){

    return false;

}

//获取用户是否重复秒杀

...

//查看商品库存是否为正

...

//秒杀

//开启事务 组成队列 商品减1 然后添加用户

$trans = $redis->multi();

$decr = $trans->decr("SP1001ID");

$addUser = $trans->sAdd('SP1001USER',$userId);

....


3.库存遗留问题

解决方案为:设置Lua脚本 通过redis直接调用lua脚本,相当于创建了一个不被干扰的队列依次执行脚本里面的redis语句。


lua脚本 保存在redis.lua中


local userId = KEYS[1]

local goodsId = KEYS[2]

local qtKey = 'SP'..goodsId..'ID'

local userKey = 'SP'..goodsId..'USER'

local userExists = redis.call('sismember',userKey,userId)

--判断用户是否存在 如果用户存在则返回状态码2

if tonumber(userExists) == 1 then

return "当前用户已存在"

end

-- --判断商品是否还有库存

local goodsNumber = redis.call('get',qtKey)

if tonumber(goodsNumber) <= 0="" then="">

return "商品库存为空"

else

--进行秒杀操作

redis.call('decr',qtKey)

redis.call('sadd',userKey,userId)

end

return "秒杀成功"


PHP调用



include_once "RedisFunction.php";

$userId = rand(100000,999999).time();

$redisFunction = new RedisFunction();

//使用redis连接池

$conf = array(

    'RA' => array('127.0.0.1',6379)   //定义Redis配置

);

$redisFunction::addServer($conf); //添加Redis配置

$redis = $redisFunction::getRedis('RA',0); //连接RA,使用默认0库


$redisLua =file_get_contents("./redis.lua");


$sha1 = $redis->script('load',$redisLua);

$res = $redis->evalSha($sha1,array($userId,1001),2);

//或者

//$redis->eval($redisLua,array($userId,1001),2);


通过这样就可以解决小并发下的库存遗留问题,在这里我进行测试的时候发现,当并发量很高的时候PHP会出现进程阻塞影响脚本执行

————————————————

版权声明:本文为CSDN博主「林笑卿」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_43193813/article/details/122001428