PHP和Redis實現在高併發下的搶購及秒殺功能

2021-08-10 14:42:06 字數 4575 閱讀 7596

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

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

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

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

第乙個問題,對於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的主從架構模式。主從架構 一主多從實現讀寫分離,主節點負責寫,並將資料同步給其他從節點,從節點負責讀,從而...