php結合redis實現高併發下的搶購、秒殺功能

来源:http://www.cnblogs.com/luokakale/archive/2017/08/02/7277096.html
-Advertisement-
Play Games

搶購、秒殺是平常很常見的場景,面試的時候面試官也經常會問到,比如問你淘寶中的搶購秒殺是怎麼實現的等等。 搶購、秒殺實現很簡單,但是有些問題需要解決,主要針對兩個問題: 1 高併發對資料庫產生的壓力 2 競爭狀態下如何解決庫存的正確減少("超賣"問題) 第一個問題,對於PHP來說很簡單,用緩存技術就可 ...


搶購、秒殺是平常很常見的場景,面試的時候面試官也經常會問到,比如問你淘寶中的搶購秒殺是怎麼實現的等等。

搶購、秒殺實現很簡單,但是有些問題需要解決,主要針對兩個問題:

1 高併發對資料庫產生的壓力

2 競爭狀態下如何解決庫存的正確減少("超賣"問題)

第一個問題,對於PHP來說很簡單,用緩存技術就可以緩解資料庫壓力,比如memcache,redis等緩存技術。

第二個問題就比較複雜點:

常規寫法:

查詢出對應商品的庫存,看是否大於0,然後執行生成訂單等操作,但是在判斷庫存是否大於0處,如果在高併發下就會有問題,導致庫存量出現負數

 

 1 <?php
 2 $conn=mysql_connect("localhost","big","123456"); 
 3 if(!$conn){ 
 4     echo "connect failed"; 
 5     exit; 
 6 } 
 7 mysql_select_db("big",$conn); 
 8 mysql_query("set names utf8");
 9  
10 $price=10;
11 $user_id=1;
12 $goods_id=1;
13 $sku_id=11;
14 $number=1;
15  
16 //生成唯一訂單
17 function build_order_no(){
18   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
19 }
20 //記錄日誌
21 function insertLog($event,$type=0){
22     global $conn;
23     $sql="insert into ih_log(event,type) 
24     values('$event','$type')"; 
25     mysql_query($sql,$conn); 
26 }
27  
28 //模擬下單操作
29 //庫存是否大於0
30 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";//解鎖 此時ih_store數據中goods_id='$goods_id' and sku_id='$sku_id' 的數據被鎖住(註3),其它事務必須等待此次事務 提交後才能執行
31 $rs=mysql_query($sql,$conn);
32 $row=mysql_fetch_assoc($rs);
33 if($row['number']>0){//高併發下會導致超賣
34     $order_sn=build_order_no();
35     //生成訂單 
36     $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
37     values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; 
38     $order_rs=mysql_query($sql,$conn); 
39      
40     //庫存減少
41     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
42     $store_rs=mysql_query($sql,$conn); 
43     if(mysql_affected_rows()){ 
44         insertLog('庫存減少成功');
45     }else{ 
46         insertLog('庫存減少失敗');
47     } 
48 }else{
49     insertLog('庫存不夠');
50 }

 

出現這種情況怎麼辦呢?來看幾種優化方法:

優化方案1:將庫存欄位number欄位設為unsigned,當庫存為0時,因為欄位不能為負數,將會返回false

1 //庫存減少
2 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0";
3 $store_rs=mysql_query($sql,$conn); 
4 if(mysql_affected_rows()){ 
5     insertLog('庫存減少成功');
6 }

優化方案2:使用MySQL的事務,鎖住操作的行

 1 <?php
 2 $conn=mysql_connect("localhost","big","123456"); 
 3 if(!$conn){ 
 4     echo "connect failed"; 
 5     exit; 
 6 } 
 7 mysql_select_db("big",$conn); 
 8 mysql_query("set names utf8");
 9  
10 $price=10;
11 $user_id=1;
12 $goods_id=1;
13 $sku_id=11;
14 $number=1;
15  
16 //生成唯一訂單號
17 function build_order_no(){
18   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
19 }
20 //記錄日誌
21 function insertLog($event,$type=0){
22     global $conn;
23     $sql="insert into ih_log(event,type) 
24     values('$event','$type')"; 
25     mysql_query($sql,$conn); 
26 }
27  
28 //模擬下單操作
29 //庫存是否大於0
30 mysql_query("BEGIN");   //開始事務
31 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此時這條記錄被鎖住,其它事務必須等待此次事務提交後才能執行
32 $rs=mysql_query($sql,$conn);
33 $row=mysql_fetch_assoc($rs);
34 if($row['number']>0){
35     //生成訂單 
36     $order_sn=build_order_no(); 
37     $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
38     values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; 
39     $order_rs=mysql_query($sql,$conn); 
40      
41     //庫存減少
42     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
43     $store_rs=mysql_query($sql,$conn); 
44     if(mysql_affected_rows()){ 
45         insertLog('庫存減少成功');
46         mysql_query("COMMIT");//事務提交即解鎖
47     }else{ 
48         insertLog('庫存減少失敗');
49     }
50 }else{
51     insertLog('庫存不夠');
52     mysql_query("ROLLBACK");
53 }

優化方案3:使用非阻塞的文件排他鎖

 1 <?php
 2 $conn=mysql_connect("localhost","root","123456"); 
 3 if(!$conn){ 
 4     echo "connect failed"; 
 5     exit; 
 6 } 
 7 mysql_select_db("big-bak",$conn); 
 8 mysql_query("set names utf8");
 9  
10 $price=10;
11 $user_id=1;
12 $goods_id=1;
13 $sku_id=11;
14 $number=1;
15  
16 //生成唯一訂單號
17 function build_order_no(){
18   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
19 }
20 //記錄日誌
21 function insertLog($event,$type=0){
22     global $conn;
23     $sql="insert into ih_log(event,type) 
24     values('$event','$type')"; 
25     mysql_query($sql,$conn); 
26 }
27  
28 $fp = fopen("lock.txt", "w+");
29 if(!flock($fp,LOCK_EX | LOCK_NB)){
30     echo "系統繁忙,請稍後再試";
31     return;
32 }
33 //下單
34 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
35 $rs=mysql_query($sql,$conn);
36 $row=mysql_fetch_assoc($rs);
37 if($row['number']>0){//庫存是否大於0
38     //模擬下單操作 
39     $order_sn=build_order_no(); 
40     $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
41     values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; 
42     $order_rs=mysql_query($sql,$conn); 
43      
44     //庫存減少
45     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
46     $store_rs=mysql_query($sql,$conn); 
47     if(mysql_affected_rows()){ 
48         insertLog('庫存減少成功');
49         flock($fp,LOCK_UN);//釋放鎖
50     }else{ 
51         insertLog('庫存減少失敗');
52     } 
53 }else{
54     insertLog('庫存不夠');
55 }
56 fclose($fp);

優化方案4:使用redis隊列,因為pop操作是原子的,即使有很多用戶同時到達,也是依次執行,推薦使用(mysql事務在高併發下性能下降很厲害,文件鎖的方式也是)

先將商品庫存如隊列

 1 <?php
 2 $store=1000;
 3 $redis=new Redis();
 4 $result=$redis->connect('127.0.0.1',6379);
 5 $res=$redis->llen('goods_store');
 6 echo $res;
 7 $count=$store-$res;
 8 for($i=0;$i<$count;$i++){
 9     $redis->lpush('goods_store',1);
10 }
11 echo $redis->llen('goods_store');

搶購、描述邏輯

 1 <?php
 2 $conn=mysql_connect("localhost","big","123456"); 
 3 if(!$conn){ 
 4     echo "connect failed"; 
 5     exit; 
 6 } 
 7 mysql_select_db("big",$conn); 
 8 mysql_query("set names utf8");
 9  
10 $price=10;
11 $user_id=1;
12 $goods_id=1;
13 $sku_id=11;
14 $number=1;
15  
16 //生成唯一訂單號
17 function build_order_no(){
18   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
19 }
20 //記錄日誌
21 function insertLog($event,$type=0){
22     global $conn;
23     $sql="insert into ih_log(event,type) 
24     values('$event','$type')"; 
25     mysql_query($sql,$conn); 
26 }
27  
28 //模擬下單操作
29 //下單前判斷redis隊列庫存量
30 $redis=new Redis();
31 $result=$redis->connect('127.0.0.1',6379);
32 $count=$redis->lpop('goods_store');
33 if(!$count){
34     insertLog('error:no store redis');
35     return;
36 }
37  
38 //生成訂單 
39 $order_sn=build_order_no();
40 $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) 
41 values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; 
42 $order_rs=mysql_query($sql,$conn); 
43  
44 //庫存減少
45 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
46 $store_rs=mysql_query($sql,$conn); 
47 if(mysql_affected_rows()){ 
48     insertLog('庫存減少成功');
49 }else{ 
50     insertLog('庫存減少失敗');
51 }

上述只是簡單模擬高併發下的搶購,真實場景要比這複雜很多,很多註意的地方

如搶購頁面做成靜態的,通過ajax調用介面

再如上面的會導致一個用戶搶多個,思路:

需要一個排隊隊列和搶購結果隊列及庫存隊列。高併發情況,先將用戶進入排隊隊列,用一個線程迴圈處理從排隊隊列取出一個用戶,判斷用戶是否已在搶購結果隊列,如果在,則已搶購,否則未搶購,庫存減1,寫資料庫,將用戶入結果隊列。

我之間做商城項目的時候,在秒殺這一塊我直接用的redis,這段時間看了看上面的幾種方法,雖然各有不同,但是實現目的都一樣的,各位自己選擇,開心就好。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 目錄(?)[+] 目錄(?)[+] 參考資料: http://263229365.iteye.com/blog/1040329 https://www.java.net/node/650758 ...
  • 1. echo count(“abcd”);輸出多少? 答案:4 2. 運行以下代碼後$a $b $c 分別是? 答案:false、0、0 4. 請用php的heredoc語法格式輸出:hello world! 5. $string = "abcdefg",那麼$string{4}的值是? 答案:e ...
  • 恢復內容開始 1.下麵程式輸出是什麼? 結果: 5 2. 寫一個函數Check_ip,使用正則表達式檢測一個IPV4的IP是否正確,正確返回1,錯誤返回0,例如 Check_ip(‘127.0.0.1’)。 3. 請指出以下代碼的錯誤之處(圈出來並加以改正): 4. 有一數組 $a=array(4, ...
  • 使用cbind()函數連接多個向量來創建數據幀。此外,使用rbind()函數合併兩個數據幀 使用merge()函數合併兩個數據幀。數據幀必須具有相同的列名稱,在其上進行合併 melt()拆分數據和cast()數據重構 連接字元串 - paste()函數 格式化數字和字元串 - format()函數 ...
  • 在前面的幾個章節中,我們的程式都是只有一個代碼段,本章我們開始學習如何編寫包含多個段的程式。 1、在代碼段中使用數據 首先考慮這樣一個問題,計算以下8個數據的和,結果存放在ax寄存器中: 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H 在前面的課程中, ...
  • Python for迴圈可以遍歷任何序列的項目,如一個列表或者一個字元串。 語法: for迴圈的語法格式如下: good 記憶體為字典,提前預習了一下字典的寫法,這裡字典跟REDIS 的鍵值很像。 ...
  • Description: 我們現在要利用 m 台機器加工 n 個工件,每個工件都有 m 道工序,每道工序都在不同的指定的機器上完成。每個工件的每道工序都有指定的加工時間。 每個工件的每個工序稱為一個操作,我們用記號 j-k 表示一個操作,其中 j 為 1 到 n 中的某個數字,為工件號; k 為 1 ...
  • Java的MVC模式簡介 MVC(Model View Control)模型-視圖-控制器 首先我們需要知道MVC模式並不是javaweb項目中獨有的,MVC是一種軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、視圖(View)和控制器(Controller),即為MV ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...