千萬(wàn)級(jí)用戶(hù)ms級(jí)抽獎(jiǎng)N名設(shè)計(jì)方案
1 需求
大促節(jié)零點(diǎn)時(shí),從關(guān)注的用戶(hù)中抽出N個(gè)人進(jìn)行禮品發(fā)放,預(yù)計(jì)全網(wǎng)超過(guò)千萬(wàn)用戶(hù)參加關(guān)注抽獎(jiǎng)活動(dòng),要求:
- 同一用戶(hù)不能重復(fù)參與
- 同一用戶(hù)不允許二次中獎(jiǎng)
2 設(shè)計(jì)方案
2.1 最原始
rand(),對(duì)每行隨機(jī)產(chǎn)生一個(gè)隨機(jī)數(shù)
select * from 關(guān)注用戶(hù)表 order by rand() desc limit,0,100
預(yù)計(jì)千萬(wàn)級(jí)別的對(duì)技術(shù)倒排大概率涼涼。
2.2 N次隨機(jī)選擇SQL
效率可以,不過(guò)要先后執(zhí)行兩條SQL,并發(fā)時(shí)有原子性問(wèn)題,且RAND函數(shù)不能保證不重復(fù)中獎(jiǎng)。
offset = SELECT FLOOR(RAND() * COUNT(*)) AS offset from 關(guān)注用戶(hù)表
select * from 關(guān)注用戶(hù)表 limit offset,1
2.3 Redis Set隨機(jī)彈出
step1:在用戶(hù)關(guān)注直播間在寫(xiě)入MySQL關(guān)注用戶(hù)表時(shí),再往Redis增加一個(gè)userlist Set,存儲(chǔ)用戶(hù)編號(hào)??杀WC用戶(hù)全局唯一(避免用戶(hù)反復(fù)的取消和關(guān)注影響數(shù)據(jù)記錄),且數(shù)據(jù)基于Hash亂序存儲(chǔ),取出的直接就是隨機(jī)值。
sadd userlist xxxid
預(yù)計(jì)用戶(hù)編號(hào)long類(lèi)型,100萬(wàn)50MB, 1000萬(wàn)用戶(hù)也僅500MB。
step2:抽獎(jiǎng)時(shí),直接使用spop,彈出隨機(jī)的100個(gè)用戶(hù)編號(hào),該操作是原子性,先彈出再返回,在加上Redist命令隊(duì)列單線(xiàn)程,不存在并發(fā)問(wèn)題,杜絕重復(fù)中獎(jiǎng)。
step3:執(zhí)行1次select in,提取數(shù)據(jù),因?yàn)槎际峭ㄟ^(guò)主鍵提取,效率快也不存在in索引失效問(wèn)題,但要注意in的數(shù)量上限是1000個(gè),超過(guò)1000個(gè)備選項(xiàng)要拆成多個(gè)in。
2.4 純Redis
內(nèi)存充足不差錢(qián)時(shí)可用。因?yàn)槌楠?jiǎng)結(jié)果頁(yè)面通常只顯示用戶(hù)昵稱(chēng),還可使用Rdis提速,用內(nèi)存換時(shí)間。
sadd userlist '123456:ikun'
sadd userlist '123456:akun'
sadd userlist '123456:bkun'
估算千萬(wàn)用戶(hù)需3G內(nèi)存,spop提取速度完全可控制在3ms內(nèi)完成,且不重復(fù)。
Redis不怕Key多,只是怕大Key。測(cè)試結(jié)果:
1000次pop執(zhí)行時(shí)間2565,即每次 pop 只需 2.5ms。