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

2019/12/27

前沿

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

🙏好了,你们看到这里。相信对抽奖代码怎么写,大概有了思路。 在这里插入图片描述

(转载本站文章请注明作者和出处 兔子托尼啊

Post Directory