面試被問到了這個問題,找了答案,記錄一下
場景:多個用戶搶一張票
假如有100W個用戶,搶一張票,除了負載均衡的辦法,怎麼支持高併發?
修改字段 :將庫存字段number字段設為unsigned,當庫存為0時,因為字段不能為負數,將會返回false;
利用悲觀鎖(不適合高併發):悲觀鎖,也就是在修改數據的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待;缺點:不適合高併發場景。每個請求都需要等待“鎖”,某些線程可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那裡。同時,這種請求會很多,瞬間增大系統的平均響應時間,結果是可用連接數被耗盡,系統陷入異常。
![假如有100W個用戶搶一張票,除了負載均衡辦法,怎麼支持高併發?](http://p2.ttnews.xyz/loading.gif)
Mysql事務:使用MySQL的事務,鎖住操作的行;
<code>//模擬下單操作//庫存是否大於0mysqli_query($conn,"BEGIN"); //開始事務$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此時這條記錄被鎖住,其它事務必須等待此次事務提交後才能執行$rs=mysqli_query($conn,$sql);$row=$rs->fetch_assoc();if($row['number']>0){ //生成訂單 $order_sn=build_order_no(); $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; $order_rs=mysqli_query($conn,$sql); //庫存減少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'"; $store_rs=mysqli_query($conn,$sql); if($store_rs){ echo '庫存減少成功'; insertLog('庫存減少成功'); mysqli_query($conn,"COMMIT");//事務提交即解鎖 }else{ echo '庫存減少失敗'; insertLog('庫存減少失敗'); }}else{ echo '庫存不夠'; insertLog('庫存不夠'); mysqli_query($conn,"ROLLBACK");}?>/<code>
FIFO隊列(不適合高併發):直接將請求放入隊列中的,採用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖 ;缺點:不適合高併發。高併發的場景下,因為請求很多,很可能一瞬間將隊列內存“撐爆”,然後系統又陷入到了異常狀態。或者設計一個極大的內存隊列,也是一種方案,但是,系統處理完一個隊列內請求的速度根本無法和瘋狂湧入隊列中的數目相比。也就是說,隊列內的請求會越積累越多,最終Web系統平均響應時候還是會大幅下降,系統還是陷入異常。
![假如有100W個用戶搶一張票,除了負載均衡辦法,怎麼支持高併發?](http://p2.ttnews.xyz/loading.gif)
文件鎖:對於日IP不高或者說併發數不是很大的應用,一般不用考慮這些!用一般的文件操作方法完全沒有問題。但如果併發高,在我們對文件進行讀寫操作時,很有可能多個進程對進一文件進行操作,如果這時不對文件的訪問進行相應的獨佔,就容易造成數據丟失
使用非阻塞的文件排他鎖:
<code>fetch_assoc();if($row['number']>0){//庫存是否大於0 //模擬下單操作 $order_sn=build_order_no(); $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; $order_rs = mysqli_query($conn,$sql); //庫存減少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'"; $store_rs = mysqli_query($conn,$sql); if($store_rs){ echo '庫存減少成功'; insertLog('庫存減少成功'); flock($fp,LOCK_UN);//釋放鎖 }else{ echo '庫存減少失敗'; insertLog('庫存減少失敗'); }}else{ echo '庫存不夠'; insertLog('庫存不夠');}fclose($fp); ?>/<code>
樂觀鎖(重要):樂觀鎖,是相對於“悲觀鎖”採用更為寬鬆的加鎖機制,大都是採用帶版本號(Version)更新。實現就是,這個數據所有請求都有資格去修改,但會獲得一個該數據的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。這樣的話,我們就不需要考慮隊列的問題,不過,它會增大CPU的計算開銷。但是,綜合來說,這是一個比較好的解決方案;
如redis的watch;<code>connect('127.0.0.1', 6379); echo $mywatchkey = $redis->get("mywatchkey");/* //插入搶購數據 if($mywatchkey>0) { $redis->watch("mywatchkey"); //啟動一個新的事務。 $redis->multi(); $redis->set("mywatchkey",$mywatchkey-1); $result = $redis->exec(); if($result) { $redis->hSet("watchkeylist","user_".mt_rand(1,99999),time()); $watchkeylist = $redis->hGetAll("watchkeylist"); echo "搶購成功!
"; $re = $mywatchkey - 1; echo "剩餘數量:".$re."
"; echo "用戶列表:"; print_r($watchkeylist); }else{ echo "手氣不好,再搶購!";exit; } }else{ // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12"); // $watchkeylist = $redis->hGetAll("watchkeylist"); echo "fail!/<code>
"; echo ".no result
"; echo "用戶列表:"; // var_dump($watchkeylist); }*/$rob_total = 100; //搶購數量if($mywatchkey<=$rob_total){ $redis->watch("mywatchkey"); $redis->multi(); //在當前連接上啟動一個新的事務。 //插入搶購數據 $redis->set("mywatchkey",$mywatchkey+1); $rob_result = $redis->exec(); if($rob_result){ $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey); $mywatchlist = $redis->hGetAll("watchkeylist"); echo "搶購成功!
"; echo "剩餘數量:".($rob_total-$mywatchkey-1)."
"; echo "用戶列表:"; var_dump($mywatchlist); }else{ $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao'); echo "手氣不好,再搶購!";exit; }}?>
閱讀更多 架構師師長 的文章