年會現場抽獎代碼到底該怎麼寫?過來人告訴你答案

前沿

說件嚴肅到事情,2019真到快要結束了。各家公司一定在緊鑼密鼓到準備年會當中了吧。年會肯定離不開抽獎吧?現場幾百上千人抽獎可千萬別出bug。如果真出bug老闆得要殺你祭天了。現場好多人看着呢。

抽獎代碼

/**
* 抽獎
*
* @author 託尼老師
* @create 2019-12-27 11:11
**/
public class LotteryTest {
/**
    * 抽獎
    *
    * @param originalRates 原始的概率列表
    * @return 物品的索引
    */
   public static int lottery(List<Double> originalRates) {
       if (originalRates == null || originalRates.isEmpty()) {
           return -1;
       }

       int size = originalRates.size();

       double sumRate = 0d;
       for (double rate : originalRates) {
           sumRate += rate;
       }

       List<Double> sortOrignalRates = new ArrayList<>(size);
       Double tempSumRate = 0d;
       for (double rate : originalRates) {
           tempSumRate += rate;
           sortOrignalRates.add(tempSumRate / sumRate);
       }

       // 根據區塊值來獲取抽取到的物品索引
       double nextDouble = Math.random();
       sortOrignalRates.add(nextDouble);
       Collections.sort(sortOrignalRates);

       return sortOrignalRates.indexOf(nextDouble);
   }
 @Test
   public void testLottery() {
       List<Reward> lotteryList = new ArrayList<>();
       Reward r = new Reward();
       r.setRate(0.03d);
       r.setRewardName("雙色球彩票一張");
       lotteryList.add(r);
       r = new Reward();
       r.setRate(0.001d);
       r.setRewardName("mac book");
       lotteryList.add(r);
       r = new Reward();
       r.setRate(0.06d);
       r.setRewardName("沒抽中");
       lotteryList.add(r);
       r = new Reward();
       r.setRate(0.001d);
       r.setRewardName("迪拜七日雙人豪華遊");
       lotteryList.add(r);
       List<Double> originalRates = new ArrayList<>();
       for (Reward e : lotteryList) {
           originalRates.add(e.getRate());
       }
       for (int i = 0; i < 20; i++) {
           System.out.println("恭喜抽中========>"
               + lotteryList.get(lottery(originalRates)).getRewardName());
       }
   }
       class Reward {
       //獎品名稱
       private String rewardName;
       //概率
       private Double rate;

       public String getRewardName() {
           return rewardName;
       }
       public void setRewardName(String rewardName) {
           this.rewardName = rewardName;
       }
       public Double getRate() {
           return rate;
       }
       public void setRate(Double rate) {
           this.rate = rate;
       }
   }
}

運行結果如下

分析結果

老闆讓做個抽獎的功能,抽獎到底該怎麼做?

  • 前端分析
    大家都知道前端顯示的數據,都是可以修改的,數據都不正確。有些抽獎邏輯代碼寫在前端文件中,這種只是針對不懂技術的人員,稍微懂些技術的肯定忽悠不住。

  • 概率

    • 公平性,公平性怎麼做?上面的代碼就是根據概率來實現,大獎概率低小獎概率低。這個就是隨機的,全憑運氣這個詞。
    • 不公平,這種的話就靠邏輯來實現來。比如,某個時間段提高中獎概率。還有一種情況直接代碼裏面判斷某個用戶直接中xx獎。這種就是所謂的內在用戶,白名單用戶。
  • 庫存
    獎品千萬要設置庫存,千萬要設置,千萬要設置。好了,重要的事情已經說了三遍了 。

講講併發的場景

以前Reids沒出場的時候,做起來真的麻煩。現在好了,很大併發我們可以交給Redis來扛了。Redis 官方數據官方表示Redis讀的速度是110000次/s,寫的速度是81000次/s 。
Redis單機支持萬級別的別分可以是很輕鬆,一般小公司足夠用了。 不會出現你們所說的超賣現象。順便說一句如果要10萬+的需求可以使用
Reids replication模式

每個人只能抽獎一次

這種場景可以看看Redis Incr 命令,Redis key再加上用戶的ID代表用戶的唯一鍵,根據自增和原子性能保證唯一還能抗大併發。
Redis Incr 命令將 key 中儲存的數字值增一。
如果 key 不存在,那麼 key 的值會先被初始化爲 0 ,然後再執行 INCR 操作。
如果值包含錯誤的類型,或字符串類型的值不能表示爲數字,那麼返回一個錯誤。
本操作的值限制在 64 位(bit)有符號數字表示之內。
我們線上很多場景都用到過Incr命令。

1個小時只能抽獎一次

這種時候用Reis 的Expire 命令,Redis Expire 命令用於設置 key 的過期時間,key 過期後將不再可用。單位以秒計。順便說個題外話,實現原理和Redis 的Set 命令差不多,
只有當然取這個key的時候,它纔會判斷當前key有沒有失效。也就是Expire命令過期策略是你Get用到這個可key的時候才判斷當前的key有沒有失效。

Nginx

其實Nginx也有對應的模塊抗併發做攔截,根據Ip來做黑名單的攔截。當有人來刷接口,大量的黑產IP過來抽獎,這個時候就要根據你們的場景來增加抽獎門檻。比如你在我們公司註冊過,或者你10天前預約過抽獎活動,這些都是例子,可以根據場景來調整。
這個就是道高一尺魔高一丈,怎麼說呢?和黃牛鬥其樂無窮。我們以前就遇到過很多IP來刷接口,黃牛來搞事情的場景。Nginx做一層防護,Redis再做一層防護,這樣經過層層的篩選
打到數據庫的流量就很少了許多。

總結

其實這個只是抽獎環節中比較簡單的場景,可能會遇到的問題。線上亂七八糟什麼情況都會出現了, 但是遇到bug了一定不要慌張。要坦然去面對,線上數據庫我們曾經遇到過一條sql引發慘案。
具體什麼情況呢?有個員工執行sql 語句的時候 where 參數沒生效。最終每個人的賬戶都增加了相同都一筆錢。最終系統賬面總資金增加了一個多億。對的,你們沒看錯一個多億。幸虧夜裏發佈,然後那天夜裏我們就忙了一宿,具體那晚的情況下次我再發個博文好好說說。

🙏好了,你們看到這裏。相信對抽獎代碼怎麼寫,大概有了思路。

如果你覺得有用的話,就點個贊👍,分享也是一種美德。祝福你在公司的年會中大獎,迪拜雙人豪華遊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章