年會現場抽獎程式碼到底該怎麼寫?過來人告訴你答案
前沿
說件嚴肅到事情,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 引數沒生效。最終每個人的賬戶都增加了相同都一筆錢。最終系統賬面總資金增加了一個多億。對的,你們沒看錯一個多億。幸虧夜裡釋出,然後那天夜裡我們就忙了一宿,具體那晚的情況下次我再發個博文好好說說。