鍍金池/ 問(wèn)答/人工智能  PHP  網(wǎng)絡(luò)安全/ 如何正確使用redis隊(duì)列處理php秒殺并發(fā)問(wèn)題?

如何正確使用redis隊(duì)列處理php秒殺并發(fā)問(wèn)題?

百度之后,找到這樣的思路:
需要一個(gè)排隊(duì)隊(duì)列和搶購(gòu)結(jié)果隊(duì)列及庫(kù)存隊(duì)列。高并發(fā)情況,先將用戶進(jìn)入排隊(duì)隊(duì)列,用一個(gè)線程循環(huán)處理從排隊(duì)隊(duì)列取出一個(gè)用戶,判斷用戶是否已在搶購(gòu)結(jié)果隊(duì)列,如果在,則已搶購(gòu),否則未搶購(gòu),庫(kù)存減1,寫(xiě)數(shù)據(jù)庫(kù),將用戶入結(jié)果隊(duì)列。

‘先將用戶進(jìn)入排隊(duì)隊(duì)列,用一個(gè)線程循環(huán)處理從排隊(duì)隊(duì)列取出一個(gè)用戶’
到底如何:"用一個(gè)線程循環(huán)處理",我就不明白該如何下手了,啥時(shí)候開(kāi)啟這個(gè)"線程"?

是不是每次用戶進(jìn)行抽獎(jiǎng),訪問(wèn)主程序就立即啟動(dòng)這個(gè)"線程"進(jìn)行循環(huán)處理隊(duì)列數(shù)據(jù)?

還是說(shuō),用戶點(diǎn)擊抽獎(jiǎng)按鈕,觸發(fā)的是執(zhí)行入隊(duì)操作,
然后,在服務(wù)器會(huì)有一個(gè)以cli方式啟動(dòng)的獨(dú)立進(jìn)程,在長(zhǎng)時(shí)間訂閱監(jiān)聽(tīng)隊(duì)列的情況,如果有數(shù)據(jù)就執(zhí)行用戶隊(duì)列的出列,然后再走下單操作?

入隊(duì)和出列,應(yīng)該是兩個(gè)不同的程序調(diào)用的吧?那如何在用戶一訪問(wèn)進(jìn)行抽獎(jiǎng),就能順利實(shí)現(xiàn)入隊(duì)和出列呢,如何在代碼層面安排好這些調(diào)用動(dòng)作的呢?

回答
編輯回答
淺時(shí)光
用一個(gè)線程循環(huán)處理",我就不明白該如何下手了,啥時(shí)候開(kāi)啟這個(gè)"線程"
  1. 這個(gè)在秒殺開(kāi)啟前開(kāi)始執(zhí)行就行,執(zhí)行過(guò)程貫穿整個(gè)秒殺過(guò)程,可以是幾個(gè)進(jìn)程也可以是一個(gè)進(jìn)程一直跑,這是出隊(duì)的過(guò)程。入隊(duì)的話就是PHP慣有模式,每次一個(gè)請(qǐng)求進(jìn)來(lái)自動(dòng)啟動(dòng)進(jìn)程,往隊(duì)列扔數(shù)據(jù)。
  2. 然后我們要明白這個(gè)隊(duì)列的意義是什么,redis的意義是什么。redis是用來(lái)扛并發(fā)用的,通過(guò)一個(gè)計(jì)數(shù)器,先查詢還有庫(kù)存就執(zhí)行入隊(duì),庫(kù)存扣完了就直接return。redis扛了一層后進(jìn)隊(duì)列數(shù)據(jù)已經(jīng)量級(jí)小很多了,隊(duì)列的作用本質(zhì)是幫DB扛并發(fā)用的,使得DB事務(wù)執(zhí)行全部串行化,避免鎖的爭(zhēng)搶降低DB性能。
2017年8月3日 18:07
編輯回答
痞性

"用一個(gè)線程循環(huán)處理",我就不明白該如何下手了,啥時(shí)候開(kāi)啟這個(gè)"線程"?

  • 我的方案是,先寫(xiě)好一個(gè)專(zhuān)門(mén)處理隊(duì)列中的數(shù)據(jù)的程序,用定時(shí)任務(wù)(linux的crontab)每分鐘調(diào)用一次此程序。

此程序使用blPop或者brPop堵塞獲取list的數(shù)據(jù)(這兩個(gè)方法的區(qū)別可看redis文檔得知),要堵塞的原因是,萬(wàn)一這一分鐘沒(méi)有數(shù)據(jù),過(guò)了30秒后數(shù)據(jù)進(jìn)來(lái)了,就要再等30秒才能處理。堵塞的最大時(shí)間我設(shè)了59秒。同時(shí)為了兼容大量數(shù)據(jù)的情況,此程序會(huì)循環(huán)從list讀數(shù)據(jù),每次讀數(shù)據(jù)用的都是堵塞方式,所以每次堵塞的時(shí)間都會(huì)根據(jù)當(dāng)前程序運(yùn)行的時(shí)長(zhǎng)動(dòng)態(tài)改變。理論上如果業(yè)務(wù)不復(fù)雜,這個(gè)程序運(yùn)行一次不會(huì)超過(guò)60秒,也就達(dá)到要求了,如果超過(guò)了60秒,也會(huì)自動(dòng)新增線程來(lái)執(zhí)行下一次循環(huán)。

你接下來(lái)的問(wèn)題有點(diǎn)不理解,你是了解大的流程的,我說(shuō)一下這個(gè)流程里的細(xì)節(jié)吧

我是用redis的list的,其它答案里有提到用鎖,如果是你這個(gè)方案,完全不需要用鎖,因?yàn)閘ist已經(jīng)為你解決了并發(fā)問(wèn)題。

只要用戶點(diǎn)了“搶”,你就把用戶的信息lPush或者rPush進(jìn)一個(gè)list,這時(shí)會(huì)返回一個(gè)int,就是告訴你這個(gè)操作之后,list里有多少條數(shù)據(jù)了,這個(gè)int是線程安全的,即使再高的并發(fā),也不會(huì)造成這個(gè)int對(duì)于這個(gè)用戶來(lái)說(shuō)已經(jīng)過(guò)時(shí),所以你可以判斷這個(gè)int有沒(méi)有超過(guò)庫(kù)存,如果超過(guò)了,直接告訴前端這個(gè)用戶錯(cuò)過(guò)秒殺了,如果否,則讓前端等待搶購(gòu)結(jié)果。(此時(shí)并不需要把超過(guò)庫(kù)存的用戶從list里刪除。庫(kù)存數(shù)建議在秒殺前查詢出來(lái)放到redis中,之后也不要修改redis的庫(kù)存數(shù),因?yàn)檫@個(gè)庫(kù)存數(shù)是專(zhuān)門(mén)用于跟list長(zhǎng)度做對(duì)比的)

接下來(lái)的步驟就根據(jù)不同的業(yè)務(wù)需求了,如果接下來(lái)要用戶填寫(xiě)補(bǔ)充信息,則最簡(jiǎn)單了:寫(xiě)一個(gè)接口接收用戶補(bǔ)充的信息,查詢list中用戶排第幾,跟庫(kù)存對(duì)比一下他是不是真的秒殺到了,然后做入庫(kù)操作。

如果接下來(lái)沒(méi)有讓用戶操作的需要了,則跟上面回答你第一個(gè)疑問(wèn)那樣,寫(xiě)一個(gè)堵塞輪詢的接口。

2017年3月11日 06:57
編輯回答
黑與白

Redis隊(duì)列可以設(shè)置隊(duì)列長(zhǎng)度,當(dāng)一個(gè)用戶連接到服務(wù)器,Redis隊(duì)列長(zhǎng)度+1,注意隊(duì)列的數(shù)據(jù)結(jié)構(gòu)是先進(jìn)先出的進(jìn)行操作。當(dāng)隊(duì)列長(zhǎng)度達(dá)到你設(shè)定的長(zhǎng)度時(shí)候,禁止Redis入隊(duì)操作。

2018年5月23日 04:26
編輯回答
嘟尛嘴

如果是1秒鐘post成百上千次的話,我的想法是:

  1. 先做插入,包含毫秒級(jí)時(shí)間戳,或者直接用自增ID
  2. 然后排序判斷插入的是第幾個(gè),如果超過(guò)總數(shù)量,則搶購(gòu)失敗,更改搶購(gòu)為已搶完,
2018年4月1日 01:54
編輯回答
墻頭草

我也很想知道這個(gè)答案

2017年9月16日 06:56
編輯回答
薄荷綠

秒殺不需要這么復(fù)雜吧,只需要有一個(gè)唯一鎖,能進(jìn)行原子增加,然后根據(jù)回饋的搶購(gòu)數(shù)來(lái)判斷是否超過(guò)了最大量,超過(guò)了就提示沒(méi)有數(shù)量就好了吧

2018年8月19日 18:54
編輯回答
有你在

高并發(fā)時(shí)可以使用鎖控制搶購(gòu)或者抽獎(jiǎng),獲取到鎖了就可以進(jìn)行購(gòu)買(mǎi)等行為,獲取不到鎖就立刻返回,像你說(shuō)的搶購(gòu)的人都加入搶購(gòu)隊(duì)列,那搶購(gòu)就有順序性了,其本身并無(wú)順序性,不能等先來(lái)的先搶購(gòu),搶購(gòu)的人有上千萬(wàn),要造一個(gè)上千萬(wàn)的隊(duì)列嗎,再說(shuō)隊(duì)列處理到最新加入隊(duì)列的,那客戶不知要等多久了

2018年3月13日 17:06
編輯回答
擱淺
$ttl = 4;
$random = mt_rand(1,1000).'-'.gettimeofday(true).'-'.mt_rand(1,1000);

$lock = fasle;
while (!$lock) {
    $lock = $redis->set('lock', $random, array('nx', 'ex' => $ttl));
}

if ($redis->get('goods.num') <= 0) {
    echo ("秒殺已經(jīng)結(jié)束");
    //刪除鎖
    if ($redis->get('lock') == $random) {
        $redis->del('lock');
    }
    return false;
}

$redis->decr('goods.num');
echo ("秒殺成功");
//刪除鎖
if ($redis->get('lock') == $random) {
    $redis->del('lock');
}
return true;

來(lái)源 http://coffeephp.com/articles...

2018年4月1日 15:47