久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      怎么解決PHP高并發(fā)(商品秒殺)問題?兩種解決方案分享

      怎么解決PHP高并發(fā)(商品秒殺)問題?下面本篇文章就來給大家分享兩種解決方案(基于mysql或基于Redis),希望對大家有所幫助。

      怎么解決PHP高并發(fā)(商品秒殺)問題?兩種解決方案分享

      秒殺會產(chǎn)生一個瞬間的高并發(fā),使用數(shù)據(jù)庫會增加數(shù)據(jù)庫的訪問壓力,也會降低訪問速度,所以我們應(yīng)該使用緩存,來降低數(shù)據(jù)庫的訪問壓力;

      可以看出這里的操作和原來的下單是不一樣的:產(chǎn)生的秒殺預(yù)訂單不會馬上寫入數(shù)據(jù)庫,會先寫入緩存,等用戶支付成功時,修改狀態(tài),寫入數(shù)據(jù)庫。
      ?
      假設(shè)num是存儲在數(shù)據(jù)庫中的字段,保存了被秒殺產(chǎn)品的剩余數(shù)量。

      if($num > 0){   //用戶搶購成功,記錄用戶信息   $num--; }

      假設(shè)在一個并發(fā)量較高的場景,數(shù)據(jù)庫中num的值為1時,可能同時會有多個進(jìn)程讀取到num為1,程序判斷符合條件,搶購成功,num減一。

      這樣會導(dǎo)致商品超發(fā)的情況,本來只有10件可以搶購的商品,可能會有超過10個人搶到,此時num在搶購?fù)瓿芍鬄樨?fù)值。

      解決該問題的方案由很多,可以簡單分為基于mysql和redis的解決方案,redis的性能要由于mysql,因此可以承載更高的并發(fā)量,不過下面介紹的方案都是基于單臺mysql和redis的,更高的并發(fā)量需要分布式的解決方案,本文沒有涉及。

      一、基于mysql的解決方案

      商品表 goods

      CREATE TABLE `goods` (  `id` int(11) NOT NULL,  `num` int(11) DEFAULT NULL,  `version` int(11) DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8

      搶購結(jié)果表 log

      CREATE TABLE `log` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `good_id` int(11) DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8

      ①悲觀鎖

      悲觀鎖的方案采用的是排他讀,也就是同時只能有一個進(jìn)程讀取到num的值。事務(wù)在提交或回滾之后,鎖會釋放,其他的進(jìn)程才能讀取。

      該方案最簡單易懂,在對性能要求不高時,可以直接采用該方案。要注意的是,SELECT … FOR UPDATE要盡可能的使用索引,以便鎖定盡可能少的行數(shù);

      排他鎖是在事務(wù)執(zhí)行結(jié)束之后才釋放的,不是讀取完成之后就釋放,因此使用的事務(wù)應(yīng)該盡可能的早些提交或回滾,以便早些釋放排它鎖。

      $this->mysqli->begin_transaction(); $result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1 FOR UPDATE"); $row = $result->fetch_assoc(); $num = intval($row['num']); if($num > 0){   usleep(100);   $this->mysqli->query("UPDATE goods SET num=num-1");   $affected_rows = $this->mysqli->affected_rows;   if($affected_rows == 1){     $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");     $affected_rows = $this->mysqli->affected_rows;     if($affected_rows == 1){       $this->mysqli->commit();       echo "success:".$num;     }else{       $this->mysqli->rollback();       echo "fail1:".$num;     }   }else{     $this->mysqli->rollback();     echo "fail2:".$num;   } }else{   $this->mysqli->commit();   echo "fail3:".$num; }

      ②樂觀鎖

      樂觀鎖的方案在讀取數(shù)據(jù)是并沒有加排他鎖,而是通過一個每次更新都會自增的version字段來解決,多個進(jìn)程讀取到相同num,然后都能更新成功的問題。在每個進(jìn)程讀取num的同時,也讀取version的值,并且在更新num的同時也更新version,并在更新時加上對version的等值判斷。

      假設(shè)有10個進(jìn)程都讀取到了num的值為1,version值為9,則這10個進(jìn)程執(zhí)行的更新語句都是UPDATE goods SET num=num-1,version=version+1 WHERE version=9,

      然而當(dāng)其中一個進(jìn)程執(zhí)行成功之后,數(shù)據(jù)庫中version的值就會變?yōu)?0,剩余的9個進(jìn)程都不會執(zhí)行成功,這樣保證了商品不會超發(fā),num的值不會小于0,但這也導(dǎo)致了一個問題,那就是發(fā)出搶購請求較早的用戶可能搶不到,反而被后來的請求搶到了。

      $result = $this->mysqli->query("SELECT num,version FROM goods WHERE id=1 LIMIT 1"); $row = $result->fetch_assoc(); $num = intval($row['num']); $version = intval($row['version']); if($num > 0){   usleep(100);   $this->mysqli->begin_transaction();   $this->mysqli->query("UPDATE goods SET num=num-1,version=version+1 WHERE version={$version}");   $affected_rows = $this->mysqli->affected_rows;   if($affected_rows == 1){     $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");     $affected_rows = $this->mysqli->affected_rows;     if($affected_rows == 1){       $this->mysqli->commit();       echo "success:".$num;     }else{       $this->mysqli->rollback();       echo "fail1:".$num;     }   }else{     $this->mysqli->rollback();     echo "fail2:".$num;   } }else{   echo "fail3:".$num; }

      ③where條件(原子操作)

      悲觀鎖的方案保證了數(shù)據(jù)庫中num的值在同一時間只能被一個進(jìn)程讀取并處理,也就是并發(fā)的讀取進(jìn)程到這里要排隊依次執(zhí)行。

      樂觀鎖的方案雖然num的值可以被多個進(jìn)程同時讀取到,但是更新操作中version的等值判斷可以保證并發(fā)的更新操作在同一時間只能有一個更新成功。

      還有一種更簡單的方案,只在更新操作時加上num>0的條件限制即可。通過where條件限制的方案雖然看似和樂觀鎖方案類似,都能夠防止超發(fā)問題的出現(xiàn),但在num較大時的表現(xiàn)還是有很大區(qū)別的。

      假如此時num為10,同時有5個進(jìn)程讀取到了num=10,對于樂觀鎖的方案由于version字段的等值判斷,這5個進(jìn)程只會有一個更新成功,這5個進(jìn)程執(zhí)行完成之后num為9;

      對于where條件判斷的方案,只要num>0都能夠更新成功,這5個進(jìn)程執(zhí)行完成之后num為5。

      $result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1"); $row = $result->fetch_assoc(); $num = intval($row['num']); if($num > 0){   usleep(100);   $this->mysqli->begin_transaction();   $this->mysqli->query("UPDATE goods SET num=num-1 WHERE num>0");   $affected_rows = $this->mysqli->affected_rows;   if($affected_rows == 1){     $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");     $affected_rows = $this->mysqli->affected_rows;     if($affected_rows == 1){       $this->mysqli->commit();       echo "success:".$num;     }else{       $this->mysqli->rollback();       echo "fail1:".$num;     }   }else{     $this->mysqli->rollback();     echo "fail2:".$num;   } }else{   echo "fail3:".$num; }

      二、基于redis的解決方案

      ①基于watch的樂觀鎖方案

      watch用于監(jiān)視一個(或多個) key ,如果在事務(wù)執(zhí)行之前這個(或這些) key 被其他命令所改動,那么事務(wù)將被打斷。

      這種方案跟mysql中的樂觀鎖方案類似,具體表現(xiàn)也是一樣的。

      $num = $this->redis->get('num'); if($num > 0) {   $this->redis->watch('num');   usleep(100);   $res = $this->redis->multi()->decr('num')->lPush('result',$num)->exec();   if($res == false){     echo "fail1";   }else{     echo "success:".$num;   } }else{   echo "fail2"; }

      ②基于list的隊列方案

      基于隊列的方案利用了redis出隊操作的原子性,搶購開始之前首先將商品編號放入響應(yīng)的隊列中,在搶購時依次從隊列中彈出操作,這樣可以保證每個商品只能被一個進(jìn)程獲取并操作,不存在超發(fā)的情況。

      該方案的優(yōu)點(diǎn)是理解和實(shí)現(xiàn)起來都比較簡單,缺點(diǎn)是當(dāng)商品數(shù)量較多是,需要將大量的數(shù)據(jù)存入到隊列中,并且不同的商品需要存入到不同的消息隊列中。

      public function init(){   $this->redis->del('goods');   for($i=1;$i<=10;$i++){     $this->redis->lPush('goods',$i);   }   $this->redis->del('result');   echo 'init done'; } public function run(){   $goods_id = $this->redis->rPop('goods');   usleep(100);   if($goods_id == false) {     echo "fail1";   }else{     $res = $this->redis->lPush('result',$goods_id);     if($res == false){       echo "writelog:".$goods_id;     }else{       echo "success".$goods_id;     }   } }

      ③基于decr返回值的方案

      如果我們將剩余量num設(shè)置為一個鍵值類型,每次先get之后判斷,然后再decr是不能解決超發(fā)問題的。

      但是redis中的decr操作會返回執(zhí)行后的結(jié)果,可以解決超發(fā)問題。我們首先get到num的值進(jìn)行第一步判斷,避免每次都去更新num的值,然后再對num執(zhí)行decr操作,并判斷decr的返回值,如果返回值不小于0,這說明decr之前是大于0的,用戶搶購成功。

      public function run(){   $num = $this->redis->get('num');   if($num > 0) {     usleep(100);     $retNum = $this->redis->decr('num');     if($retNum >= 0){       $res = $this->redis->lPush('result',$retNum);       if($res == false){         echo "writeLog:".$retNum;       }else{         echo "success:".$retNum;       }     }else{       echo "fail1";     }   }else{     echo "fail2";   } }

      ④基于setnx的排它鎖方案

      redis沒有像mysql中的排它鎖,但是可以通過一些方式實(shí)現(xiàn)排它鎖的功能,就類似php使用文件鎖實(shí)現(xiàn)排它鎖一樣。

      setnx實(shí)現(xiàn)了exists和set兩個指令的功能,若給定的key已存在,則setnx不做任何動作,返回0;若key不存在,則執(zhí)行類似set的操作,返回1。

      我們設(shè)置一個超時時間timeout,每隔一定時間嘗試setnx操作,如果設(shè)置成功就是獲得了相應(yīng)的鎖,執(zhí)行num的decr操作,操作完成刪除相應(yīng)的key,模擬釋放鎖的操作。

      public function run(){   do {     $res = $this->redis->setnx("numKey",1);     $this->timeout -= 100;     usleep(100);   }while($res == 0 && $this->timeout>0);   if($res == 0){     echo 'fail1';   }else{     $num = $this->redis->get('num');     if($num > 0) {       $this->redis->decr('num');       usleep(100);       $res = $this->redis->lPush('result',$num);       if($res == false){         echo "fail2";       }else{         echo "success:".$num;       }     }else{       echo "fail3";     }     $this->redis->del("numKey");   } }

      推薦學(xué)習(xí):《PHP視頻教程》

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號