本文解決上文第一個(gè)問(wèn)題汛蝙,即超賣(mài)問(wèn)題故俐,主要是通過(guò)redis鎖機(jī)制控制并發(fā)
只有獲得鎖的進(jìn)程才能進(jìn)行商品秒殺活動(dòng)胁澳,否則都排隊(duì)等待鹿驼,所有的操作(鎖機(jī)制欲低,用戶購(gòu)買(mǎi)記錄等)都基于redis實(shí)現(xiàn),最后再持久化到數(shù)據(jù)庫(kù)中
php代碼更改如下
/**
* 開(kāi)始搶購(gòu)
* @author jinanav 2021年10月5日21:54:07
* @return
*/
public function index(){
//持久化數(shù)據(jù)
$this->persistence();exit();
$redis = new \Redis();
$redis->connect("127.0.0.1");
//初始化數(shù)據(jù)
// for ($i = 0; $i < 10;$i++){
// $redis->lPush("orderseckill",1);
// }
// $redis->set("seckillTotal",10);
// $redis->del("lock");
//var_dump($redis->get("seckillTotal"));
//登錄流程假定統(tǒng)
$uid = Request::get("uid");
//固定商品id
$gId = 1;
$lockId = md5(uniqid().rand(100,999));
//監(jiān)聽(tīng)key
$redis->watch("seckillTotal");
//循環(huán)搶鎖,進(jìn)行并發(fā)控制
while (($total = intval($redis->get("seckillTotal"))) > 0){
//開(kāi)啟redis事務(wù)
$redis->multi();
//開(kāi)始上鎖
if($redis->set("lock",$lockId, array('nx', 'ex' => 10))){
//講商品id與用戶id寫(xiě)入到隊(duì)列中
$guid = $gId."_".$uid;
//推入到隊(duì)列中(也可以用rabbitMQ實(shí)現(xiàn))
$redis->lPush("seckill",$guid);
$redis->set("seckillTotal",$total - 1);
//解鎖
// if($redis->get("lock") == $lockId){
// $redis->del("lock");
// }
//為了保證解鎖過(guò)程的原子性畜晰,推薦使用以下方式釋放鎖
$script = 'if redis.call("get","lock") == "'.$lockId.'"
then
return redis.call("del","lock")
else
return 0
end ';
$redis->eval($script);
//提交redis事務(wù)
if( $redis->exec()){
$this->success("搶購(gòu)成功");
}
}
//睡眠50毫秒
usleep(50000);
}
$this->error("沒(méi)有搶到任何商品");
}
/**
* 數(shù)據(jù)持久化
* @author jinanav 2021/10/6 1:07
* @return
*/
public function persistence(){
$redis = new \Redis();
$redis->connect("127.0.0.1");
$queue = "seckill";
while($redis->lLen($queue)){
$guid = $redis->rPop($queue);
if(!empty($guid)){
$dataArray = explode("_",$guid);
$uid = $dataArray[1];
$goods = Db::name("goods")->where(['id'=>$dataArray[0]])->find();
try {
Db::startTrans();
Db::name('order')->insert([
'order_num' => md5(uniqid().time().rand(10,99)),
'total_cash'=> $goods['price'],
'user_id' => $uid,
'title' => $goods['goods_title'],
'g_u_id' => $guid
]);
Db::name("goods")->where(['id'=>$goods['id']])->update(['seckill_inventory'=>$goods['seckill_inventory']-1]);
Db::commit();
}catch (\Exception $e){
Db::rollback();
}
}
}
$this->success("入庫(kù)成功");
}
看go并發(fā)1000個(gè)用戶進(jìn)行秒殺活動(dòng)
查看數(shù)據(jù)庫(kù)結(jié)果
10條數(shù)據(jù)砾莱,,沒(méi)有超賣(mài)
利用redis鎖機(jī)制實(shí)現(xiàn)了并發(fā)控制凄鼻,但是腊瑟,明顯編碼程度略復(fù)雜,下一文將用go的協(xié)成實(shí)現(xiàn)并發(fā)控制块蚌,編碼更簡(jiǎn)單闰非,更易理解
其實(shí)也可以通過(guò)redis的隊(duì)列實(shí)現(xiàn)并發(fā)控制,由于隊(duì)列的操作是原子性的(可以提前生成令牌列表總數(shù)匈子,令牌消耗盡了說(shuō)明商品也搶購(gòu)?fù)炅?河胎,一堆請(qǐng)求過(guò)來(lái)依舊是要排隊(duì)的,代碼如下
/**
* 開(kāi)始搶購(gòu)
* @author jinanav 2021年10月5日21:54:07
* @return
*/
public function index(){
//持久化數(shù)據(jù)
//$this->persistence();exit();
$redis = new \Redis();
$redis->connect("127.0.0.1");
//初始化數(shù)據(jù)
// for ($i = 0; $i < 10;$i++){
// $redis->lPush("orderseckill",1);
// }
// var_dump($redis->lLen("orderseckill"));
// exit();
//登錄流程假定統(tǒng)
$uid = Request::get("uid");
//固定商品id
$gId = 1;
if($redis->lPop("orderseckill")){
//講商品id與用戶id寫(xiě)入到隊(duì)列中
$guid = $gId."_".$uid;
//推入到隊(duì)列中(也可以用rabbitMQ實(shí)現(xiàn))
$redis->lPush("seckill",$guid);
$this->success("搶購(gòu)成功");
}else{
$this->error("沒(méi)有搶到任何商品");
}
$this->error("沒(méi)有搶到任何商品");
}
go模仿100個(gè)用戶進(jìn)行請(qǐng)求
數(shù)據(jù)庫(kù)結(jié)果