亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 語言 > PHP > 正文

PHP商品秒殺問題解決方案實例詳解【mysql與redis】

2024-05-05 00:10:29
字體:
來源:轉載
供稿:網友

本文實例講述了PHP商品秒殺問題解決方案。分享給大家供大家參考,具體如下:

引言

假設num是存儲在數據庫中的字段,保存了被秒殺產品的剩余數量。

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

假設在一個并發量較高的場景,數據庫中num的值為1時,可能同時會有多個進程讀取到num為1,程序判斷符合條件,搶購成功,num減一。這樣會導致商品超發的情況,本來只有10件可以搶購的商品,可能會有超過10個人搶到,此時num在搶購完成之后為負值。

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

基于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

搶購結果表 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

悲觀鎖

悲觀鎖的方案采用的是排他讀,也就是同時只能有一個進程讀取到num的值。事務在提交或回滾之后,鎖會釋放,其他的進程才能讀取。該方案最簡單易懂,在對性能要求不高時,可以直接采用該方案。要注意的是,SELECT … FOR UPDATE要盡可能的使用索引,以便鎖定盡可能少的行數;排他鎖是在事務執行結束之后才釋放的,不是讀取完成之后就釋放,因此使用的事務應該盡可能的早些提交或回滾,以便早些釋放排它鎖。

$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;}

樂觀鎖

樂觀鎖的方案在讀取數據是并沒有加排他鎖,而是通過一個每次更新都會自增的version字段來解決,多個進程讀取到相同num,然后都能更新成功的問題。在每個進程讀取num的同時,也讀取version的值,并且在更新num的同時也更新version,并在更新時加上對version的等值判斷。假設有10個進程都讀取到了num的值為1,version值為9,則這10個進程執行的更新語句都是UPDATE goods SET num=num-1,version=version+1 WHERE version=9,然而當其中一個進程執行成功之后,數據庫中version的值就會變為10,剩余的9個進程都不會執行成功,這樣保證了商品不會超發,num的值不會小于0,但這也導致了一個問題,那就是發出搶購請求較早的用戶可能搶不到,反而被后來的請求搶到了。

$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條件(原子操作)

悲觀鎖的方案保證了數據庫中num的值在同一時間只能被一個進程讀取并處理,也就是并發的讀取進程到這里要排隊依次執行。樂觀鎖的方案雖然num的值可以被多個進程同時讀取到,但是更新操作中version的等值判斷可以保證并發的更新操作在同一時間只能有一個更新成功。

還有一種更簡單的方案,只在更新操作時加上num>0的條件限制即可。通過where條件限制的方案雖然看似和樂觀鎖方案類似,都能夠防止超發問題的出現,但在num較大時的表現還是有很大區別的。假如此時num為10,同時有5個進程讀取到了num=10,對于樂觀鎖的方案由于version字段的等值判斷,這5個進程只會有一個更新成功,這5個進程執行完成之后num為9;對于where條件判斷的方案,只要num>0都能夠更新成功,這5個進程執行完成之后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用于監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。這種方案跟mysql中的樂觀鎖方案類似,具體表現也是一樣的。

$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出隊操作的原子性,搶購開始之前首先將商品編號放入響應的隊列中,在搶購時依次從隊列中彈出操作,這樣可以保證每個商品只能被一個進程獲取并操作,不存在超發的情況。該方案的優點是理解和實現起來都比較簡單,缺點是當商品數量較多是,需要將大量的數據存入到隊列中,并且不同的商品需要存入到不同的消息隊列中。

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設置為一個鍵值類型,每次先get之后判斷,然后再decr是不能解決超發問題的。但是redis中的decr操作會返回執行后的結果,可以解決超發問題。我們首先get到num的值進行第一步判斷,避免每次都去更新num的值,然后再對num執行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中的排它鎖,但是可以通過一些方式實現排它鎖的功能,就類似php使用文件鎖實現排它鎖一樣。

setnx實現了exists和set兩個指令的功能,若給定的key已存在,則setnx不做任何動作,返回0;若key不存在,則執行類似set的操作,返回1。我們設置一個超時時間timeout,每隔一定時間嘗試setnx操作,如果設置成功就是獲得了相應的鎖,執行num的decr操作,操作完成刪除相應的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");  }}

上述代碼都在本地測試通過,完整代碼地址:https://github.com/qianshou/SeckillSolution

希望本文所述對大家PHP程序設計有所幫助。


注:相關教程知識閱讀請移步到PHP教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲精品久久久久久下一站| 国产精品天天狠天天看| 日韩在线免费视频观看| 国产精品成人免费电影| 欧美在线观看网站| 久久久天堂国产精品女人| 九九热最新视频//这里只有精品| 国模精品一区二区三区色天香| 欧美激情综合色| 久久中文久久字幕| 精品久久久久久中文字幕大豆网| 日韩精品中文字幕有码专区| 欧美尤物巨大精品爽| 精品国产乱码久久久久久天美| 日本一区二区三区在线播放| 久久福利网址导航| 国产亚洲一区二区精品| 国产精品久久久久久一区二区| 欧美日韩在线视频观看| 精品国内亚洲在观看18黄| 午夜精品久久久久久久99黑人| 成人免费视频网| 亚洲国产精品成人av| 日韩中文综合网| 亚洲国产另类 国产精品国产免费| 68精品久久久久久欧美| 日韩毛片在线看| 中文字幕日韩精品在线| 91中文字幕一区| 日韩av在线电影网| 日韩av电影中文字幕| 久久久免费观看视频| 成人激情视频在线| 国产精品男女猛烈高潮激情| 亚洲国产精品va在线看黑人| 欧美性猛交xxxx免费看久久久| 欧美三级欧美成人高清www| 免费99精品国产自在在线| 成人黄色影片在线| 91系列在线观看| 亚洲自拍偷拍网址| 日韩一中文字幕| 伊人伊成久久人综合网小说| 久久久精品一区二区| 日韩在线视频网| 欧美制服第一页| 国产欧美 在线欧美| 色悠悠久久久久| 麻豆国产精品va在线观看不卡| 91夜夜揉人人捏人人添红杏| 亚洲第一免费播放区| 亚洲综合色av| 亚洲欧洲在线播放| 国产精品18久久久久久首页狼| 色狠狠久久aa北条麻妃| 国产精品一二区| 亚洲第一网站免费视频| 亚洲欧美福利视频| 国产精品日韩欧美| 国产午夜精品全部视频播放| 欧美激情乱人伦一区| 在线视频免费一区二区| 国产精品电影在线观看| 91麻豆国产精品| 国产ts人妖一区二区三区| 久久久久久久久久久av| 4k岛国日韩精品**专区| 青青a在线精品免费观看| 久久久天堂国产精品女人| 国产亚洲激情在线| 日韩精品在线免费观看| 国产suv精品一区二区三区88区| 国产精品视频99| 国产精品第一第二| 亚洲欧美在线第一页| 亚洲福利视频久久| 日韩成人黄色av| 一道本无吗dⅴd在线播放一区| 亚洲视频一区二区| 日韩在线观看免费av| 国产综合视频在线观看| 欧美性黄网官网| 97碰在线观看| 亚洲国产精品电影在线观看| 国产精品第一视频| 国产精品入口日韩视频大尺度| 欧美黑人一级爽快片淫片高清| 久久久久久国产| 俺去了亚洲欧美日韩| 亚洲电影免费观看高清完整版在线观看| 国产成人精品电影| 精品国偷自产在线视频99| 伊人亚洲福利一区二区三区| 亚洲免费av网址| 精品露脸国产偷人在视频| 色播久久人人爽人人爽人人片视av| 成人激情av在线| 亚洲第一精品福利| 欧美日韩亚洲国产一区| 久久99精品久久久久久琪琪| 久久五月天色综合| 精品爽片免费看久久| 国产高清在线不卡| 欧美日韩亚洲激情| 国产精品极品尤物在线观看| 国产精品久久久久9999| 久久精品国产亚洲7777| 91精品中国老女人| 欧美多人乱p欧美4p久久| 最新中文字幕亚洲| 久久精品视频免费播放| 欧美日韩亚洲一区二| 欧美日韩中文字幕日韩欧美| 欧美床上激情在线观看| 久久久www成人免费精品| 亚洲一区二区少妇| 国产区精品在线观看| 亚洲精品日韩欧美| 精品福利视频导航| 久久精品欧美视频| 亚洲3p在线观看| 日韩成人激情影院| 最近2019中文免费高清视频观看www99| 欧美精品亚州精品| 国产亚洲欧洲高清一区| 精品动漫一区二区| 国产精品久久久久av免费| 国产精品999999| 成人久久18免费网站图片| 中文字幕日韩欧美精品在线观看| 国产成人精品免高潮在线观看| 日韩电影在线观看免费| 亚洲精品成人av| 亚洲欧美在线看| 538国产精品一区二区在线| 国产日本欧美在线观看| 久久人91精品久久久久久不卡| 成人免费福利在线| 亚洲国产另类 国产精品国产免费| 国产精品久久久久久亚洲调教| 国产精品日韩欧美综合| 欧美激情视频三区| 亚洲国产一区二区三区在线观看| 欧美猛交免费看| 2019国产精品自在线拍国产不卡| 色综合久久中文字幕综合网小说| …久久精品99久久香蕉国产| 精品视频久久久久久| 欧美日韩性视频| 国产精品91久久久久久| 在线激情影院一区| 91精品国产综合久久久久久蜜臀| 国产精品中文字幕久久久| 成人网欧美在线视频| 欧美激情在线一区| 亚洲视频在线免费观看| 自拍亚洲一区欧美另类| 一本色道久久综合狠狠躁篇怎么玩| 中文字幕国产亚洲2019| 亚洲网在线观看| 亚洲欧洲xxxx| 日韩精品久久久久| 欧美精品福利视频| 久久精品99久久香蕉国产色戒|