秒杀的步骤

1.设置库存

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

//设置 key 为 SP1001ID 的商品库存为10
set SP1001ID 10
2用户参与秒杀

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

1.redis链接超时问题
解决方案:设置redis连接池

PHP设置连接池:https://www.cnblogs.com/daizhongxing/p/13840211.html
注意:PHP为脚本语言,脚本执行完毕Redis 连接自动关闭,所以连接池并不能跨脚本使用。
使用连接池的原因:在高并发情况下,频繁地创建和释放 Redis 连接会对性能有较大影响。
连接池的原理:预先创建多个 Redis 连接,在进行 Redis 操作时直接获取已经创建的连接进行操作,操作完成后不会释放,后续其他 Redis 操作可以继续使用。这样就避免了频繁的 Redis 连接和释放。

<?php

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调用

<?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会出现进程阻塞影响脚本执行

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐