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

时间:2022-08-31 16:34:00

突然想写点关于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;

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