PHP+Redis事務(wù)解決高并發(fā)下商品超賣(mài)問(wèn)題(推薦)
對(duì)于一些有一定用戶(hù)量的電商網(wǎng)站,如果只是單純的使用關(guān)系型數(shù)據(jù)庫(kù)(如MySQL、Oracle)來(lái)做搶購(gòu),對(duì)數(shù)據(jù)庫(kù)的壓力是非常大的,而且如果不使用好數(shù)據(jù)庫(kù)的鎖機(jī)制,還會(huì)導(dǎo)致商品、優(yōu)惠券超賣(mài)的問(wèn)題。我所在的公司也遇到了同樣的問(wèn)題,問(wèn)題發(fā)生在優(yōu)惠券被超量搶購(gòu)上,在問(wèn)題發(fā)生后我們開(kāi)始想辦法解決問(wèn)題,由于自己使用redis比較多,我準(zhǔn)備使用redis來(lái)解決這個(gè)問(wèn)題。利用redis的高性能和事務(wù)特性來(lái)解決線(xiàn)上優(yōu)惠券被超庫(kù)存搶購(gòu)的問(wèn)題,下面我給出我臨時(shí)解決這個(gè)問(wèn)題的第一版的偽代碼,去掉了一些細(xì)節(jié):
/** * 搶優(yōu)惠券(秒殺) * @param int $couponId 商品ID * @param int $uid 用戶(hù)ID * @return bool */function secKill($couponId, $uid){ //1.初始化Redis連接 $redis = new Redis(); if (!$redis->connect(’127.0.0.1’, 6379)) { trigger_error(’Redis連接出錯(cuò)!!!’, E_USER_ERROR); } else { echo ’連接正常<br>’; } //秒殺商品的庫(kù)存key $key = ’secKill:’.$couponId.’:stock’; $redis->watch($key); //獲取庫(kù)存 $stock = $redis->get($key); //秒殺未開(kāi)始,表示庫(kù)存為null if (!$stock && !is_numeric($stock)) { echo ’秒殺未開(kāi)始’; return false; } //判斷庫(kù)存,如果庫(kù)存大于0,則減庫(kù)存,將該成功秒殺用戶(hù)加入哈希表,如果小于等于0,秒殺結(jié)束 if ($stock <= 0) { echo ’秒殺已結(jié)束’; return false; } //用戶(hù)已經(jīng)成功秒殺過(guò)一次了,不允許再次參與秒殺 if ($redis->sIsMember(’secKill:’.$couponId.’:uid’, $uid)) { echo ’秒殺失敗’; return false; } //代碼走到這里,說(shuō)明該用戶(hù)是第一次參與秒殺,將庫(kù)存減一,然后把這個(gè)人放到已搶到的集合表 //multi(),返回一個(gè)redis對(duì)象,并進(jìn)入multi-mode模式,一旦進(jìn)入multi-mode模式,以后調(diào)用的所有方法都會(huì)返回相同的對(duì)象, //直到exec()方法被調(diào)用。 $result = $redis->multi()->decr($key)->sAdd(’secKill:’.$couponId.’:uid’, $uid)->exec(); if (empty($result)) {//事務(wù)被取消 echo ’秒殺失敗’; return false; } //搶券成功,將優(yōu)惠券ID和UID放入到隊(duì)列中,由一個(gè)單獨(dú)的進(jìn)程隊(duì)列來(lái)消費(fèi)隊(duì)列里的數(shù)據(jù),向用戶(hù)推送搶到的優(yōu)惠券 $redis->lPush(’couponOrder’, $couponId.’+’.$uid); $redis->close(); return true;}$couponId = 11211;$uid = mt_rand(1, 100);secKill($couponId, $uid);
首先,我模擬設(shè)置優(yōu)惠券ID為11211的優(yōu)惠券庫(kù)存為10個(gè)。
然后,我們使用ab工具來(lái)模擬1000次請(qǐng)求,50并發(fā)量來(lái)測(cè)試
ab -n 1000 -c 50 www.test.com/
然后我們通過(guò)Redis Desktop Manager來(lái)查看一些Redis的結(jié)果
couponOrder隊(duì)列里已經(jīng)有了10個(gè)用戶(hù)的信息了
并且優(yōu)惠券的剩余數(shù)量也是0了,不再是負(fù)數(shù)了
同時(shí),用戶(hù)搶券集合里也保存了10個(gè)用戶(hù)的UID信息。
上面這串代碼解決了兩個(gè)問(wèn)題:
解決了瞬時(shí)的大量查詢(xún)到數(shù)據(jù)庫(kù)上給數(shù)據(jù)庫(kù)造成很大壓力的問(wèn)題,流量都被攔截在了redis緩存層 解決了優(yōu)惠券被超庫(kù)存搶購(gòu)的問(wèn)題但是,這段代碼也存在一定的問(wèn)題:
沒(méi)有使用redis連接池,頻繁創(chuàng)建新的redis有一定的性能影響 由于使用了事務(wù),每一次并發(fā)請(qǐng)求中只會(huì)有一個(gè)用戶(hù)搶券成功,該并發(fā)請(qǐng)求中的其它用戶(hù)都會(huì)失敗,只能等第二次并發(fā) 同樣還是事務(wù)導(dǎo)致的庫(kù)存遺留問(wèn)題,如果有10個(gè)商品,1000次請(qǐng)求每次200并發(fā)量,5次并發(fā)請(qǐng)求就完成了1000次請(qǐng)求,但是只會(huì)有5個(gè)用戶(hù)成功搶到,如果沒(méi)有后續(xù)的請(qǐng)求,會(huì)導(dǎo)致庫(kù)存還有5份存量提示:在消費(fèi)隊(duì)列里,如果優(yōu)惠券發(fā)放失敗,一定要立即記錄并短信通知運(yùn)營(yíng)管理人員,看看是否能重發(fā)或者通過(guò)后臺(tái)手動(dòng)定向推送給用戶(hù)。
所以,后續(xù)我又使用了lua腳本和redis配合一起來(lái)解決了這個(gè)問(wèn)題。具體代碼,我會(huì)后續(xù)整理處理補(bǔ)充完整。
總結(jié)
到此這篇關(guān)于PHP+Redis事務(wù)解決高并發(fā)下商品超賣(mài)問(wèn)題的文章就介紹到這了,更多相關(guān)php redis 解決高并發(fā)下商品超賣(mài)內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. PHP腳本的10個(gè)技巧(8)2. IntelliJ IDEA設(shè)置條件斷點(diǎn)的方法步驟3. XML入門(mén)精解之結(jié)構(gòu)與語(yǔ)法4. idea設(shè)置自動(dòng)導(dǎo)入依賴(lài)的方法步驟5. idea設(shè)置代碼格式化的方法步驟6. PyTorch模型轉(zhuǎn)TensorRT是怎么實(shí)現(xiàn)的?7. IntelliJ IDEA設(shè)置編碼格式的方法8. idea自定義快捷鍵的方法步驟9. idea重置默認(rèn)配置的方法步驟10.排行榜PHP腳本的10個(gè)技巧(8) 1. XML入門(mén)精解之結(jié)構(gòu)與語(yǔ)法 2. IntelliJ IDEA設(shè)置條件斷點(diǎn)的方法步驟 3. PyTorch模型轉(zhuǎn)TensorRT是怎么實(shí)現(xiàn)的? 4. idea設(shè)置代碼格式化的方法步驟 5. IntelliJ IDEA設(shè)置編碼格式的方法 6. idea設(shè)置自動(dòng)導(dǎo)入依賴(lài)的方法步驟 7. idea自定義快捷鍵的方法步驟 8. IntelliJ IDEA配置Tomcat服務(wù)器的方法 9. idea刪除項(xiàng)目的操作方法 10.