代码改变世界

年会现场抽奖代码到底该怎么写?过来人告诉你答案

2019-12-27 14:50  兔子托尼啊  阅读(1867)  评论(6编辑  收藏

前沿

说件严肃到事情,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 参数没生效。最终每个人的账户都增加了相同都一笔钱。最终系统账面总资金增加了一个多亿。对的,你们没看错一个多亿。幸亏夜里发布,然后那天夜里我们就忙了一宿,具体那晚的情况下次我再发个博文好好说说。

🙏好了,你们看到这里。相信对抽奖代码怎么写,大概有了思路。

如果你觉得有用的话,就点下面的好文要顶按钮,分享也是一种美德。祝福你在公司的年会中大奖,迪拜双人豪华游。