之前对redis高并发下的抢购/秒杀一直是迷迷糊糊的,还是不认真的原因。今天在慕课网学习了一下视频,讲了些基础,也照着他的代码写了案例(有趣的是:免费的课程,老师讲的太简单,甚至还有漏洞):
1:数据库连接(单例模式的):
class Db{
private static $_instance;
private static $_dbConnect;
private $_config=array(
'host'=>'172.22.32.107',
'user'=>'itop',
'password'=>'itop',
'db'=>'test_zd_practice'
);
private function __construct()
{}
private function __clone()
{}
public static function getInstance(){
if(!self::$_instance instanceof self){
self::$_instance=new self();
}
return self::$_instance;
}
public function connection(){
self::$_dbConnect=mysqli_connect($this->_config['host'],$this->_config['user'],$this->_config['password']);
if(!self::$_dbConnect){
throw new Exception("mysql connect error".mysql_error());
}
mysqli_set_charset(self::$_dbConnect,'utf8');
mysqli_select_db(self::$_dbConnect,$this->_config['db']);
return self::$_dbConnect;
}
}
2:redis_queue.php
$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$redis_name="order";
$count=10;
for($i=0;$i<100;$i++){
$uid=rand(10000,99999);
if($redis->lLen($redis_name)<10){
$redis->rPush($redis_name,$uid."%".microtime());
echo $uid."秒杀成功";
}else{
echo $uid."秒杀已结束";
}
}
$redis->close()
3:save_db.php
include 'db.php';
$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$redis_name="order";
while(1){
$user=$redis->lPop($redis_name);
if(!$user || $user=='nil'){
sleep(2);
continue;
}
$arr=explode("%",$user);
$uid=$arr[0];
$time_stamp=$arr[1];
$db=Db::getInstance();
$conn=$db->connection();
$result=mysqli_query($conn,"insert into redis_queue(uid,time_stamp) values ($uid,$time_stamp)");
if(!$result){
$redis->lPush($redis_name,$user);
}
sleep(2);
}
$redis->close();
redis_queue.php 在高并发下是有问题的:
在抢购进行到一定程度,假如现在已经有9个人抢购成功,又来了3个用户同时抢购,这时if条件将会被绕过(条件同时被满足了),这三个用户都能抢购成功。而实际上只剩下一件库存可以抢了。
在高并发下,很多看似不大可能是问题的,都成了实际产生的问题了。要解决“超抢/超卖”的问题,核心在于保证检查库存时的操作是依次执行的,再形象的说就是把“多线程”转成“单线程”。即使有很多用户同时到达,也是一个个检查并给与抢购资格,一旦库存抢尽,后面的用户就无法继续了。
我们需要使用redis的原子操作来实现这个“单线程”。首先我们把库存存在store这个列表中,假设有10件库存,就往列表中push10个数,这个数没有实际意义,仅仅只是代表一件库存。抢购开始后,每到来一个用户,就从store中pop一个数,表示用户抢购成功。当列表为空时,表示已经被抢光了。因为列表的pop操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:
$redis=new Redis();
$redis->connect('127.0.0.1',6379);
$store="store";
$order="order";
/*到“/抢购的时间到了”可放入其他地方,抢购时间一般是固定的,可在抢购之前就执行这段代码 */
$count=10;
$len = $redis->lLen($store);
$count = $num - $len;
for ($i = 0; $i < $count; $i++) {
$redis->lpush($store, 1);
}
//抢购的时间到了
for($i=0;$i<100;$i++) {
$uid = rand(10000, 99999);
$count = $redis->lpop($store); /* 模拟抢购操作,抢购前判断redis队列库存量 */
if (!$count){
echo '秒杀已结束';
}else{
$result = $redis->lpush($order,$uid."%".microtime());
if($result){
echo '秒杀成功';
}
}
}
$redis->close();
本文涉及部分转载,参照:https://www.cnblogs.com/phpper/p/7085663.html 里面写得很好哦!