十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
這篇文章主要介紹“PHP高并發(fā)之怎么解決商品庫(kù)存超賣(mài)問(wèn)題”,在日常操作中,相信很多人在PHP高并發(fā)之怎么解決商品庫(kù)存超賣(mài)問(wèn)題問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”P(pán)HP高并發(fā)之怎么解決商品庫(kù)存超賣(mài)問(wèn)題”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供惠安網(wǎng)站建設(shè)、惠安做網(wǎng)站、惠安網(wǎng)站設(shè)計(jì)、惠安網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、惠安企業(yè)網(wǎng)站模板建站服務(wù),10余年惠安做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
對(duì)于第一個(gè)問(wèn)題,使用緩存來(lái)處理,避免直接操作數(shù)據(jù)庫(kù),例如使用 redis。
對(duì)于第二個(gè)問(wèn)題,需要重點(diǎn)說(shuō)明。
常規(guī)寫(xiě)法:查詢出對(duì)應(yīng)商品的庫(kù)存,判斷庫(kù)存數(shù)量否大于 0,然后執(zhí)行生成訂單等操作,但是在判斷庫(kù)存是否大于 0 處,如果在高并發(fā)下就會(huì)有問(wèn)題,導(dǎo)致庫(kù)存量出現(xiàn)負(fù)數(shù)。
把如下表數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫(kù)中
/* Navicat MySQL Data Transfer Source Server : 01 本地localhost Source Server Version : 50553 Source Host : localhost:3306 Source Database : test Target Server Type : MYSQL Target Server Version : 50553 File Encoding : 65001 Date: 2020-11-06 14:31:35 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for products -- ---------------------------- DROP TABLE IF EXISTS `products`; CREATE TABLE `products` ( `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'ID', `title` varchar(50) DEFAULT NULL COMMENT '貨品名稱', `store` int(11) DEFAULT '0' COMMENT '貨品庫(kù)存', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='貨品表'; -- ---------------------------- -- Records of products -- ---------------------------- INSERT INTO `products` VALUES ('1', '稻花香大米', '20'); -- ---------------------------- -- Table structure for order_log -- ---------------------------- DROP TABLE IF EXISTS `order_log`; CREATE TABLE `order_log` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `content` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '日志內(nèi)容', `c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; -- ---------------------------- -- Table structure for order -- ---------------------------- DROP TABLE IF EXISTS `order`; CREATE TABLE `order` ( `oid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '訂單號(hào)', `product_id` int(11) DEFAULT '0' COMMENT '商品ID', `number` int(11) DEFAULT '0' COMMENT '購(gòu)買(mǎi)數(shù)量', `c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`oid`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='訂單表';
0) { sleep(1); //step4 更新商品庫(kù)存數(shù)量(減去下單數(shù)量) $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "更新成功"; //step5 生成訂單號(hào)創(chuàng)建訂單 $oid = build_order_no(); create_order($oid, $product_id, $buy_num); insertLog('庫(kù)存減少成功,下單成功'); } else { echo "更新失敗"; insertLog('庫(kù)存減少失敗'); } } else { echo "沒(méi)有庫(kù)存"; insertLog('庫(kù)存不夠'); } function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } } /** * 生成唯一訂單號(hào) */ function build_order_no() { return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); } function create_order($oid, $product_id, $number) { global $con; $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')"; mysqli_query($con, $sql); } /** * 記錄日志 */ function insertLog($content) { global $con; $sql = "INSERT INTO `order_log` (content) values('$content')"; mysqli_query($con, $sql); }
因?yàn)閹?kù)存字段不能為負(fù)數(shù),在下單后更新商品庫(kù)存時(shí),如果出現(xiàn)負(fù)數(shù)將返回 false
0) { sleep(1); //step4 更新商品庫(kù)存數(shù)量(減去下單數(shù)量) $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "更新成功"; //step5 生成訂單號(hào)創(chuàng)建訂單 $oid = build_order_no(); create_order($oid, $product_id, $buy_num); insertLog('庫(kù)存減少成功,下單成功'); } else { // 如果出現(xiàn)負(fù)數(shù)將返回false echo "更新失敗"; insertLog('庫(kù)存減少失敗'); } } else { //商品已經(jīng)搶購(gòu)?fù)? echo "沒(méi)有庫(kù)存"; insertLog('庫(kù)存不夠'); } function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } } /** * 生成唯一訂單號(hào) */ function build_order_no() { return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); } function create_order($oid, $product_id, $number) { global $con; $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')"; mysqli_query($con, $sql); } /** * 記錄日志 */ function insertLog($content) { global $con; $sql = "INSERT INTO `order_log` (content) values('$content')"; mysqli_query($con, $sql); }
在下單處理過(guò)程中,使用 mysql 的事務(wù)將正在下單商品行數(shù)據(jù)鎖定
0) { sleep(1); //step4 更新商品庫(kù)存數(shù)量(減去下單數(shù)量) $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "更新成功"; //step5 生成訂單號(hào)創(chuàng)建訂單 $oid = build_order_no(); create_order($oid, $product_id, $buy_num); insertLog('庫(kù)存減少成功,下單成功'); mysqli_query($con, "COMMIT");//事務(wù)提交即解鎖 } else { echo "更新失敗"; insertLog('庫(kù)存減少失敗'); mysqli_query($con, "ROLLBACK");//事務(wù)回滾即解鎖 } } else { //商品已經(jīng)搶購(gòu)?fù)? echo "沒(méi)有庫(kù)存"; insertLog('庫(kù)存不夠'); mysqli_query($con, "ROLLBACK");//事務(wù)回滾即解鎖 } function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } } /** * 生成唯一訂單號(hào) */ function build_order_no() { return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); } function create_order($oid, $product_id, $number) { global $con; $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')"; mysqli_query($con, $sql); } /** * 記錄日志 */ function insertLog($content) { global $con; $sql = "INSERT INTO `order_log` (content) values('$content')"; mysqli_query($con, $sql); }
在處理下單請(qǐng)求的時(shí)候,用 flock 鎖定一個(gè)文件,如果鎖定失敗說(shuō)明有其他訂單正在處理,此時(shí)要么等待要么直接提示用戶” 服務(wù)器繁忙”,計(jì)數(shù)器存儲(chǔ)搶購(gòu)的商品數(shù)量,避免查詢數(shù)據(jù)庫(kù)。
阻塞 (等待) 模式:并發(fā)時(shí),當(dāng)有第二個(gè)用戶請(qǐng)求時(shí),會(huì)等待第一個(gè)用戶請(qǐng)求完成、釋放鎖,獲得文件鎖之后,程序才會(huì)繼續(xù)運(yùn)行下去。
0) { //處理訂單 sleep(1); //step4 更新商品庫(kù)存數(shù)量(減去下單數(shù)量) $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "更新成功"; //step5 生成訂單號(hào)創(chuàng)建訂單 $oid = build_order_no(); create_order($oid, $product_id, $buy_num); insertLog('庫(kù)存減少成功,下單成功'); } else { echo "更新失敗"; insertLog('庫(kù)存減少失敗'); } } else { //商品已經(jīng)搶購(gòu)?fù)? echo "沒(méi)有庫(kù)存"; insertLog('庫(kù)存不夠'); } flock($fp, LOCK_UN); //釋放鎖 } fclose($fp); function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } } /** * 生成唯一訂單號(hào) */ function build_order_no() { return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); } function create_order($oid, $product_id, $number) { global $con; $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')"; mysqli_query($con, $sql); } /** * 記錄日志 */ function insertLog($content) { global $con; $sql = "INSERT INTO `order_log` (content) values('$content')"; mysqli_query($con, $sql); }
非阻塞模式:并發(fā)時(shí),第一個(gè)用戶請(qǐng)求,拿得文件鎖之后。后面請(qǐng)求的用戶直接返回系統(tǒng)繁忙,請(qǐng)稍后再試
0) { //處理訂單 sleep(1); //step4 更新商品庫(kù)存數(shù)量(減去下單數(shù)量) $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "更新成功"; //step5 生成訂單號(hào)創(chuàng)建訂單 $oid = build_order_no(); create_order($oid, $product_id, $buy_num); insertLog('庫(kù)存減少成功,下單成功'); } else { echo "更新失敗"; insertLog('庫(kù)存減少失敗'); } } else { //商品已經(jīng)搶購(gòu)?fù)? echo "沒(méi)有庫(kù)存"; insertLog('庫(kù)存不夠'); } flock($fp, LOCK_UN); //釋放鎖 } else { //系統(tǒng)繁忙,請(qǐng)稍后再試 echo "系統(tǒng)繁忙,請(qǐng)稍后再試"; insertLog('系統(tǒng)繁忙,請(qǐng)稍后再試'); } fclose($fp); function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } } /** * 生成唯一訂單號(hào) */ function build_order_no() { return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); } function create_order($oid, $product_id, $number) { global $con; $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')"; mysqli_query($con, $sql); } /** * 記錄日志 */ function insertLog($content) { global $con; $sql = "INSERT INTO `order_log` (content) values('$content')"; mysqli_query($con, $sql); }
因?yàn)?pop 操作是原子的,即使有很多用戶同時(shí)到達(dá),也是依次執(zhí)行,推薦使用
mysql 事務(wù)在高并發(fā)下性能下降很厲害,文件鎖的方式也是
先將商品庫(kù)存到 redis 隊(duì)列
connect('127.0.0.1', 6379); $key = 'goods_store_' . $product_id; $res = $redis->llen($key); $count = $store - $res; for ($i=0; $i<$count; $i++) { $redis->lpush($key, 1); } echo $redis->llen($key); function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } }
2. 搶購(gòu)、秒殺邏輯
connect('127.0.0.1',6379); $count = $redis->lpop('goods_store_' . $product_id); if (!$count) { insertLog('error:no store redis'); return '秒殺結(jié)束,沒(méi)有商品庫(kù)存了'; } sleep(1); //step3 更新商品庫(kù)存數(shù)量(減去下單數(shù)量) $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "更新成功"; //step4 生成訂單號(hào)創(chuàng)建訂單 $oid = build_order_no(); create_order($oid, $product_id, $buy_num); insertLog('庫(kù)存減少成功,下單成功'); } else { echo "更新失敗"; insertLog('庫(kù)存減少失敗'); } function db() { global $con; $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } } /** * 生成唯一訂單號(hào) */ function build_order_no() { return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); } function create_order($oid, $product_id, $number) { global $con; $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')"; mysqli_query($con, $sql); } /** * 記錄日志 */ function insertLog($content) { global $con; $sql = "INSERT INTO `order_log` (content) values('$content')"; mysqli_query($con, $sql); }
redis 樂(lè)觀鎖防止超賣(mài)
connect("127.0.0.1", 6379); $redis->watch('sales');//樂(lè)觀鎖 監(jiān)視作用 set() 初始值0 $sales = $redis->get('sales'); $n = 20;// 庫(kù)存 if ($sales >= $n) { exit('秒殺結(jié)束'); } //redis開(kāi)啟事務(wù) $redis->multi(); $redis->incr('sales'); //將 key 中儲(chǔ)存的數(shù)字值增一 ,如果 key 不存在,那么 key 的值會(huì)先被初始化為 0 ,然后再執(zhí)行 INCR 操作。 $res = $redis->exec(); //成功1 失敗0 if ($res) { //秒殺成功 $con = new mysqli('localhost','root','root','test'); if (!$con) { echo "數(shù)據(jù)庫(kù)連接失敗"; } $product_id = 1;// 商品ID $buy_num = 1;// 購(gòu)買(mǎi)數(shù)量 sleep(1); $sql = "update products set store=store-{$buy_num} where id={$product_id}"; if (mysqli_query($con, $sql)) { echo "秒殺完成"; } } else { exit('搶購(gòu)失敗'); }
到此,關(guān)于“PHP高并發(fā)之怎么解決商品庫(kù)存超賣(mài)問(wèn)題”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!