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