REDIS实践之使用watch完成秒杀抢购功能

突然想写点关于redis业务实现的一些东西,想起来很早之前看过一个关于用watch完成秒杀功能的案例,然后又翻出来看了看,不看还好, 一看发现这个实现逻辑是有问题了,随便改吧改吧,希望不要被误导

$redis = new Redis();
$redis->pconnect('127.0.0.1', 6379, 2.5);
echo "Connection to server sucessfully\n";
//check whether server is running or not
// echo "Server is running: " . $redis->ping();

$redis->select(7);

$rob_total = 100;   //抢购数量  

$mywatchkey = $redis->get("mywatchkey");
if($mywatchkey === false){
    $redis->set("mywatchkey", $rob_total);
    $mywatchkey = $rob_total;
}

if($mywatchkey <= 0){
    file_put_contents('message.txt', "you are late, red packet was gone\n", FILE_APPEND );
    exit;
}

$redis->watch("mywatchkey");  
$mywatchkey = $redis->get("mywatchkey");  
if($mywatchkey > 0 ){  
    
    $ret = $redis->multi();  
    
    //插入抢购数据  [去重]
    $ret = $ret->hSetNx("mywatchlist","user_id_".uniqid().mt_rand(1, 1000),time()); 
    $ret = $ret->decr("mywatchkey");
    $rob_result = $ret->exec();  
    if($rob_result){  
        $mywatchlist = $redis->hGetAll("mywatchlist");  
        echo "抢购成功!
"; echo "剩余数量:". ($mywatchkey-1)."
"; echo "用户列表:
";  
        var_dump($mywatchlist);

        file_put_contents('message.txt', "bug sucess! remaining num:". $redis->get("mywatchkey") . "\n", FILE_APPEND );

    }else{

        $redis->discard();
        file_put_contents('message.txt', "bug failure! unlucky go on \n", FILE_APPEND );

    }
}else{
    file_put_contents('message.txt', "red packet was gone\n", FILE_APPEND );
}

// $redis->close();
exit;


首先连接 这里用的pconnet 并设置2.5s的超时限制  抢购肯定是在短时间内大量的请求 所以还是持久化好点

然后这个mywatchkey取值的点也要注意下

还有这里采用的hash存放用户id,

以前我有  json(['userid'=>00001, 'time'=>time()]) 后放list的情况, 还是跟业务吧,思路多样化


对于这种实现思路 一定注意:

大量的用户涌入, 不是说你是前【$rob_total = 100】个就一定能抢到,后面的用户就没有红包可抢了,这种要基于并发考虑, 比如说前100个用户同时涌入,其中一个用户获得这个 watch key  其他用户就只能 "bug failure! unlucky go on" 继续自主加入抢的队列或者放弃了

而且watch mulit exec 会拖慢性能, 需要根据业务量 考虑相应的策略


如果对于前100用户的抢购业务策略,可以考虑list+set方式,不过也有一定的局限性

首先有个红包队列, 这里简单模拟下:

//note 预存红包
for ($i=0; $i < 1000; $i++) { 
    $redis->lpush('000|redpacket', time(). sprintf("%04d", mt_rand(0, 1000)));
}

然后给就是等待放出时间开始抢的业务了:

//note begin buy
$userid = mt_rand(0, 10000);

$redOne = $redis->rpop('000|redpacket');
if($redOne){
    if($redis->sadd('000|redusers', $userid)){
        $redis->lpush('000|consumeredlist', json_encode(['redOne'=>$redOne, 'userid'=>$userid, 'time'=>microtime(true)]));
        
        file_put_contents('message.txt', "SUCESS: You bug success\n", FILE_APPEND );            

    }else{
        //note: if user have already bought, Put the red packet back list
        if(!$redis->lpush('000|redpacket', $redOne)) $redis->lpush('000|redpacket', $redOne);

        file_put_contents('message.txt', "NOTICE: You have already bought\n", FILE_APPEND );            
    }
}else{
    file_put_contents('message.txt', "NOTICE: you are late, red packet was gone\n", FILE_APPEND );    
}

exit;

其实主要就是要根据业务场景,考虑具体实现思路,这里只是对其中一些点做些浅显的介绍


你可能感兴趣的:(Redis实践)