搶購、秒殺是平常很常見的場景,面試的時候面試官也經常會問到,比如問你**中的搶購秒殺是怎麼實現的等等。
搶購、秒殺實現很簡單,但是有些問題需要解決,主要針對兩個問題:
一、高併發對資料庫產生的壓力
二、競爭狀態下如何解決庫存的正確減少("超賣"問題)
第乙個問題,對於php來說很簡單,用快取技術就可以緩解資料庫壓力,比如memcache,redis等快取技術。
第二個問題就比較複雜點:
常規寫法:
查詢出對應商品的庫存,看是否大於0,然後執行生成訂單等操作,但是在判斷庫存是否大於0處,如果在高併發下就會有問題,導致庫存量出現負數。
<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn)
mysql_select_db("big",$conn);
mysql_query("set names utf8");
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
//生成唯一訂單
function build_order_no()
//記錄日誌
function insertlog($event,$type=0)
//模擬下單操作
//庫存是否大於0
$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),其它事務必須等待此次事務 提交後才能執行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row['number']>0) where sku_id='$sku_id'";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows())else
}else
複製**
出現這種情況怎麼辦呢?來看幾種優化方法:
優化方案1:將庫存欄位number欄位設為unsigned,當庫存為0時,因為字段不能為負數,將會返回false
1 //庫存減少
2 $sql="update ih_store set number=number- where sku_id='$sku_id' and number>0";
3 $store_rs=mysql_query($sql,$conn);
4 if(mysql_affected_rows())
複製**
優化方案2:使用mysql的事務,鎖住操作的行
<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn)
mysql_select_db("big",$conn);
mysql_query("set names utf8");
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
//生成唯一訂單號
function build_order_no()
//記錄日誌
function insertlog($event,$type=0)
//模擬下單操作
//庫存是否大於0
mysql_query("begin"); //開始事務
$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' for update";//此時這條記錄被鎖住,其它事務必須等待此次事務提交後才能執行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row['number']>0) where sku_id='$sku_id'";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows())else
}else
複製**
優化方案3:使用非阻塞的檔案排他鎖
<?php
$conn=mysql_connect("localhost","root","123456");
if(!$conn)
mysql_select_db("big-bak",$conn);
mysql_query("set names utf8");
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
//生成唯一訂單號
function build_order_no()
//記錄日誌
function insertlog($event,$type=0)
$fp = fopen("lock.txt", "w+");
if(!flock($fp,lock_ex | lock_nb))
//下單
$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row['number']>0) where sku_id='$sku_id'";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows())else
}else
fclose($fp);
複製**
先將商品庫存如佇列
<?php
$store=1000;
$redis=new redis();
$result=$redis->connect('127.0.0.1',6379);
$res=$redis->llen('goods_store');
echo $res;
$count=$store-$res;
for($i=0;$i<$count;$i++)
echo $redis->llen('goods_store');
複製**
搶購、描述邏輯
<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn)
mysql_select_db("big",$conn);
mysql_query("set names utf8");
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
//生成唯一訂單號
function build_order_no()
//記錄日誌
function insertlog($event,$type=0)
//模擬下單操作
//下單前判斷redis佇列庫存量
$redis=new redis();
$result=$redis->connect('127.0.0.1',6379);
$count=$redis->lpop('goods_store');
if(!$count)
//生成訂單
$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=mysql_query($sql,$conn);
//庫存減少
$sql="update ih_store set number=number- where sku_id='$sku_id'";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows())else
複製**
上述只是簡單模擬高併發下的搶購,真實場景要比這複雜很多,很多注意的地方,如搶購頁面做成靜態的,通過ajax呼叫介面。
再如上面的會導致乙個使用者搶多個,思路:
需要乙個排隊佇列和搶購結果佇列及庫存佇列。高併發情況,先將使用者進入排隊佇列,用乙個執行緒迴圈處理從排隊佇列取出乙個使用者,判斷使用者是否已在搶購結果佇列,如果在,則已搶購,否則未搶購,庫存減1,寫資料庫,將使用者入結果佇列。
Redis高併發和高可用
如何保證 redis 的高併發和高可用?redis 的主從複製原理能介紹一下麼?redis 的哨兵原理能介紹一下麼?其實問這個問題,主要是考考你,redis 單機能承載多高併發?如果單機扛不住如何擴容扛更多的併發?redis 會不會掛?既然 redis 會掛那怎麼保證 redis 是高可用的?其實針...
php結合redis實現高併發下的搶購 秒殺功能
搶購 秒殺是如今很常見的乙個應用場景,主要需要解決的問題有兩個 1 高併發對資料庫產生的壓力 2 競爭狀態下如何解決庫存的正確減少 超賣 問題 對於第乙個問題,已經很容易想到用快取來處理搶購,避免直接運算元據庫,例如使用redis。重點在於第二個問題 優化方案1 將庫存欄位number欄位設為uns...
Redis如何做到高併發和高可用
1 高併發 單機的redis的qps一般在幾萬 官方說法可以達到10萬 所以對於十萬 百萬甚至千萬級別的qps,單機的redis肯定不能支撐這麼大的訪問量。解決此類高併發問題,可以採用redis的主從架構模式。主從架構 一主多從實現讀寫分離,主節點負責寫,並將資料同步給其他從節點,從節點負責讀,從而...