首頁  >  問答  >  主體

關於使用redis防高並發超中獎品設定過期時間的一個疑問

業務

大轉盤抽獎活動 獎品分實物和紅包 限制用戶只能中一個實物

使用redis防同一用戶並發超領實物 即中了多個實物
獲得獎金池

AwardPool chooseAwardFromPool(){ //得到奖池
    // 查询所有有效奖品
    // 若用户之前已中了实物 排除实物奖品
    if(hasWinedRealObject && award.type==实物)
        continue;
    //...
}
award = chooseAwardFromPool(pool); //从奖池中随机选择一个奖品

若用戶還未抽中實物, 且同一用戶並發進入, 存在隨機選擇的獎品都為實物的可能, 如同一用戶10個並發請求進來, 其中3個請求碰巧隨機選擇的獎品均為實物, 於是該使用者就能中3個實物, 於是需要引入redis來防並發超中實物. 如下所示

//选中奖品后处理
if(award.type == 实物){ // 若奖品为实物
    key = "user_"+userId+"_实物_count";
    count = redis.incr(key);
    if(count == 1){
        redis.expire(key, 10*60); //设置过期时间10分钟
    }
    if(count > 1){ //同一用户中了多个实物
        award = 未中奖; //此时默认替换为未中奖奖品
    }
}

過期時間我始終不知該如何評估, 設定多長時間合適, 因為基本上是針對惡意用戶並發請求才引入redis的, 正常用戶的正常頁面操作無需做任何處理, 因為若前一次中了實物,後面再來抽獎的話, 一開始取得獎池時就會排除掉實物獎品, 故後面抽獎獎池中壓根就沒有實物獎品了, 也就不會中實物了.
為什麼設置10分鐘呢? 因為我覺得兩個並發請求--且是均中了實物的兩個請求--不可能執行redis.incr(key)時相隔了10分鐘, 如下所示

#请求1  用户尚未中实物 从奖池中随机返回了一个实物奖品
award = chooseAwardFromPool(pool); //从奖池中随机选择一个奖品

#请求n  用户尚未中实物 也从奖池中随机返回了一个实物奖品
award = chooseAwardFromPool(pool); //从奖池中随机选择一个奖品

#请求1 执行redis操作
count = redis.incr(key);

#请求n 执行redis操作
count = redis.incr(key);

我覺得10分鐘能夠保證請求n執行redis操作時, key不會過期, 故能夠防超中實物.

但又不是很篤定, 怎覺得存在請求2執行時key會過期的情況, 但又想不出什麼情況下會有這樣的情況.
請求數並發量特別大的情況下會存在這種可能嗎? 如

ab -n 1000000 -c 1000 -T "application/x-www-form-urlencoded" -p post_draw http://localhost:8080/draw

如請求1過來的時候隨機選中了一個實物, 等到請求n過來的時候, 請求1還沒有提交到數據庫中, 於是請求n有可能隨機返回一個實物獎品, 等到請求n執行redis.incr(key )時, 已經過了10分鐘了, 於是請求n仍能中實物.於是同一用戶中了兩個實物.

滿天的星座滿天的星座2757 天前853

全部回覆(1)我來回復

  • PHP中文网

    PHP中文网2017-04-25 09:04:25

    1.按照你的設計用redis來處理,為什麼不把時間直接設為永久呢?等請求A事物提交後再清除,接著就全走你正常的邏輯判斷
    2.不用redis,借助數據庫實現多並發控制,首先你肯定有個獎池表,裡面會有字段表示獲獎人,每次用戶中獎時會去更新獎池表設定獲獎人,然後你在進行sql更新的時候加上實物獎數量控制條件

    回覆
    0
  • 取消回覆