前沿
說件嚴肅到事情,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 參數沒生效。最終每個人的賬戶都增加了相同都一筆錢。最終系統賬面總資金增加了一個多億。對的,你們沒看錯一個多億。幸虧夜里發布,然后那天夜里我們就忙了一宿,具體那晚的情況下次我再發個博文好好說說。
🙏好了,你們看到這里。相信對抽獎代碼怎么寫,大概有了思路。