By东关

REDIS实践之使用watch完成秒杀抢购功能
2021-01-05

突然想写点关于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 "抢购成功!<br/>";
        echo "剩余数量:". ($mywatchkey-1)."<br/>";
        echo "用户列表:<pre>";
        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;
其实主要就是要根据业务场景,考虑具体实现思路,这里只是对其中一些点做些浅显的介绍

你必须 登录 才能发表评论.

  • 还没有人留下脚印噢,快来踩踩叭