php电商交押金的逻辑,PHP高并发下抢购、秒杀功能的超卖问题
?搶購、秒殺是電商系統比較常見的功能,高并發下一般需要解決兩個問題:
高并發下數據庫的壓力
高并發競爭下出現超賣問題
對于第一個問題,一般可以通過緩存、分庫分表、主從等可以解決,這兒主要說說超賣問題。
一般我們都是得到商品庫存,檢查庫存是否大于0,然后執行生成訂單等操作。但是在高并發情況下,會導致庫存量出現負數。
一、常規方法
error_reporting(E_ALL ^ E_DEPRECATED);
//生成唯一訂單
if(!function_exists('build_order_no'))
{
function build_order_no()
{
return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
}
//記錄日志
if(!function_exists('insert_log'))
{
function insert_log($event, $type = 0){
global $conn;
$sql = "INSERT INTO ck_log(event,type) VALUES('$event','$type')";
mysql_query($sql,$conn);
}
}
//模擬下單操作
$conn = mysql_connect('127.0.0.1','root','root') or die('mysql connect fail.');
mysql_select_db('ck_shop');
mysql_query('set names utf8');
$goods_id = 1;
$sku_id = 11;
$user_id = 1;
$price = 10;
$number = 1;
$sql = "SELECT `number` FROM `ck_store` WHERE goods_id='{$goods_id}' AND sku_id='{$sku_id}'";
$res = mysql_query($sql,$conn);
$row = mysql_fetch_assoc($res);
if($row['number'] > 0) //并發情況下會導致超賣
{
//生成訂單
$order_sn = build_order_no();
$sql = "INSERT INTO `ck_order`(`order_sn`,`user_id`,`goods_id`,`sku_id`,`price`)
VALUES('{$order_sn}','{$user_id}','{$goods_id}','{$sku_id}','{$price}')";
$order_res = mysql_query($sql,$conn);
if($order_res)
{
//減庫存
$sql = "UPDATE `ck_store` SET `number`=`number`-{$number} WHERE `sku_id`={$sku_id}";
$store_res = mysql_query($sql);
if(mysql_affected_rows())
{
insert_log('庫存減少成功!');
}
else
{
insert_log('庫存減少失敗!');
}
}
else
{
insert_log('生成訂單失敗!');
}
}
else
{
insert_log('庫存不足!');
}
二、數據表優化方法
將庫存字段number設置為unsigned,當庫存為0時。因為字段不能為負數,返回。
//減庫存
$sql = "UPDATE `ck_store` SET `number`=`number`-{$number} WHERE `sku_id`={$sku_id} AND `number` > 0";
$store_res = mysql_query($sql);
if(mysql_affected_rows())
{
insert_log('庫存減少成功!');
}
三、數據庫事務優化方法
使用mysql innodb引擎事務,鎖住操作行。
try{
mysql_query('BEGIN');
$sql = "SELECT `number` FROM `ck_store` WHERE goods_id='{$goods_id}' AND sku_id='{$sku_id}'";
$res = mysql_query($sql,$conn);
$row = mysql_fetch_assoc($res);
if($row['number'] > 0) //并發情況下會導致超賣
{
//生成訂單
$order_sn = build_order_no();
$sql = "INSERT INTO `ck_order`(`order_sn`,`user_id`,`goods_id`,`sku_id`,`price`)
VALUES('{$order_sn}','{$user_id}','{$goods_id}','{$sku_id}','{$price}')";
$order_res = mysql_query($sql,$conn);
if($order_res)
{
//減庫存
$sql = "UPDATE `ck_store` SET `number`=`number`-{$number} WHERE `sku_id`={$sku_id}";
$store_res = mysql_query($sql);
if(mysql_affected_rows())
{
insert_log('庫存減少成功!');
mysql_query('COMMIT');
}
else
{
insert_log('庫存減少失敗!');
}
}
else
{
insert_log('生成訂單失敗!');
}
}
else
{
insert_log('庫存不足!');
mysql_query('ROLLBACK');
}
}catch(\Exception $e)
{
mysql_query('ROLLBACK');
}
mysql_query('END');
四、非阻塞文件排它鎖方法
try{
$file = md5(__FILE__.$user_id).'.lock';
$fp = fopen($file, 'w+');
if(!flock($fp,LOCK_EX | LOCK_NB))
{
die('系統繁忙...');
}
$sql = "SELECT `number` FROM `ck_store` WHERE goods_id='{$goods_id}' AND sku_id='{$sku_id}'";
$res = mysql_query($sql,$conn);
$row = mysql_fetch_assoc($res);
if($row['number'] > 0) //并發情況下會導致超賣
{
//生成訂單
$order_sn = build_order_no();
$sql = "INSERT INTO `ck_order`(`order_sn`,`user_id`,`goods_id`,`sku_id`,`price`)
VALUES('{$order_sn}','{$user_id}','{$goods_id}','{$sku_id}','{$price}')";
$order_res = mysql_query($sql,$conn);
if($order_res)
{
//減庫存
$sql = "UPDATE `ck_store` SET `number`=`number`-{$number} WHERE `sku_id`={$sku_id}";
$store_res = mysql_query($sql);
if(mysql_affected_rows())
{
insert_log('庫存減少成功!');
flock($fp,LOCK_UN);
}
else
{
insert_log('庫存減少失敗!');
}
}
else
{
insert_log('生成訂單失敗!');
}
}
else
{
insert_log('庫存不足!');
}
}catch(\Exception $e)
{
insert_log('未知錯誤!');
}
fclose($fp);
五、Redis緩存方法
因為pop操作是原子的,所以即使很多用戶到達,也是一次執行。推薦使用這個方法,mysql事務和排它鎖在高并發下性能下降很多。
先將商品庫存入到redis隊列。
$store = 500;
$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++)
{
$redis->lpush('goods_store',1);
}
echo $redis->llen('goods_store');
搶購、描述邏輯
error_reporting(E_ALL ^ E_DEPRECATED);
//生成唯一訂單
if(!function_exists('build_order_no'))
{
function build_order_no()
{
return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
}
//記錄日志
if(!function_exists('insert_log'))
{
function insert_log($event, $type = 0){
global $conn;
$sql = "INSERT INTO ck_log(event,type) VALUES('$event','$type')";
mysql_query($sql,$conn);
}
}
//模擬下單操作
$conn = mysql_connect('127.0.0.1','root','root') or die('mysql connect fail.');
mysql_select_db('ck_shop');
mysql_query('set names utf8');
$goods_id = 1;
$sku_id = 11;
$user_id = 1;
$price = 10;
$number = 1;
try{
//下單前判斷redis隊列庫存量
$redis = new Redis();
$result = $redis->connect('127.0.0.1',6379);
$count = $redis->lpop('goods_store');
if(!$count){
insertLog('error:no store redis');
die('搶購完畢.');
}
//生成訂單
$order_sn = build_order_no();
$sql = "INSERT INTO `ck_order`(`order_sn`,`user_id`,`goods_id`,`sku_id`,`price`)
VALUES('{$order_sn}','{$user_id}','{$goods_id}','{$sku_id}','{$price}')";
$order_res = mysql_query($sql,$conn);
if($order_res)
{
//減庫存
$sql = "UPDATE `ck_store` SET `number`=`number`-{$number} WHERE `sku_id`={$sku_id}";
$store_res = mysql_query($sql);
if(mysql_affected_rows())
{
insert_log('庫存減少成功!');
}
else
{
insert_log('庫存減少失敗!');
}
}
else
{
insert_log('生成訂單失敗!');
}
}catch(\Exception $e)
{
insert_log('未知錯誤!');
}
fclose($fp);
以上就是模擬高并發請購、秒殺的簡單實現,真實場景通常比這個要復雜的多。如:
1.頁面靜態化,通過AJAX交換數據。
2.上述會出現一個用戶多搶的情況,一般需要三個隊列,一個排隊隊列,一個搶購結果隊列,一個庫存隊列。高并發下,先將用戶入隊到排隊隊列,用一個線程處理排隊隊列出隊,判斷該用戶是否在搶購結果隊列。如果在,則已搶購;否則,庫存減1,寫庫,將用戶入隊到搶購結果隊列。
總結
以上是生活随笔為你收集整理的php电商交押金的逻辑,PHP高并发下抢购、秒杀功能的超卖问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql很简单,Mysql入门很简单
- 下一篇: matlab中(1 )什么意思,matl