【抽奖项目】|第二篇

前言:

        高并发的活动预热肯定不可以在数据库操作,需要redis,特别是这种秒杀活动更是需要注意,所以可以在高并发的前夕先进行活动预热。

 思路:

       1、 通过定时任务调度每分钟查询数据库也没有需要预热的活动

        2、采用分布式锁防止任务重复调度

        3、查询到预热活动需要信息全部进行redis存储

        4、生成令牌桶

                  细节:生成总奖品个数个令牌

                             每个令牌生成开始到结束时间的一个随机数

                              乘上1000,在额外加上一个三位数的随机数  ------防止奖品过多令牌重复

                              把令牌放入令牌桶

                              设置令牌和奖品的关系

        5、先按照时间大小排序,在压入redis

        6、改变预热状态

    @Scheduled(cron = "0 * * * * ?")
    public void execute() {
        //TODO 缓存预热

        // 获取当前时间的Calendar实例
        Calendar calendar = Calendar.getInstance();
        // 清除毫秒部分
        calendar.set(Calendar.MILLISECOND, 0);
        // 获取不带毫秒的Date对象
        Date now = calendar.getTime();
        //分布式锁,防止重复启动任务
        if (!redisUtil.setNx("game_task_"+now.getTime(),1,60L)){
            log.info("task started by another server!");
            return;
        }
        //查询将来1分钟内要开始的活动
        QueryWrapper<CardGame> gameQueryWrapper = new QueryWrapper<>();
        //开始时间大于当前时间
        gameQueryWrapper.gt("starttime",now);
        //小于等于(当前时间+1分钟)
        gameQueryWrapper.le("starttime",DateUtils.addMinutes(now,1));
        List<CardGame> list = gameService.list(gameQueryWrapper);
        if(list.size() == 0){
            //没有查到要开始的活动
            log.info("没有查到要开始的活动");
            return;
        }
        log.info("需要缓存预热的活动个数:{}",list.size());
        //有相关活动数据,则将活动数据预热,进redis
        list.forEach(game ->{
            //活动开始时间
            long start = game.getStarttime().getTime();
            //活动结束时间
            long end = game.getEndtime().getTime();
            //计算活动结束时间到现在还有多少秒,作为redis key过期时间
            long expire = (end - now.getTime())/1000;
//            long expire = -1; //永不过期
            //活动持续时间(ms)
            long duration = end - start;

            Map queryMap = new HashMap();
            queryMap.put("gameid",game.getId());


            //活动基本信息
            game.setStatus(1);
            redisUtil.set(RedisKeys.INFO+game.getId(),game,-1);
            log.info("活动ID:{},名称:{},开始:{},结束{}", game.getId(),game.getTitle(),game.getStarttime(),game.getEndtime());

            //活动奖品信息
            List<CardProductDto> products = gameLoadService.getByGameId(game.getId());
            Map<Integer,CardProduct> productMap = new HashMap<>(products.size());
            products.forEach(p -> {
                productMap.put(p.getId(),p);
            });

            //奖品数量等配置信息
            List<CardGameProduct> gameProducts = gameProductService.listByMap(queryMap);

            //令牌桶
            List<Long> tokenList = new ArrayList();
            gameProducts.forEach(cgp ->{
                //生成amount个start到end之间的随机时间戳做令牌
                for (int i = 0; i < cgp.getAmount(); i++) {
                    long rnd = start + new Random().nextInt((int)duration);
                    //为什么乘1000,再额外加一个随机数呢? - 防止时间段奖品多时重复
                    //记得取令牌判断时间时,除以1000,还原真正的时间戳
                    long token = rnd * 1000 + new Random().nextInt(999);
                    //将令牌放入令牌桶
                    tokenList.add(token);
                    //token到实际奖品之间建立映射关系
                    redisUtil.set(RedisKeys.TOKEN + game.getId() +"_"+token,productMap.get(cgp.getProductid()),expire);
                }
            });
            //排序后放入redis队列
            Collections.sort(tokenList);
            log.info("load tokens:{}",tokenList);

            //从右侧压入队列,从左到右,时间戳逐个增大
            redisUtil.rightPushAll(RedisKeys.TOKENS + game.getId(),tokenList);
            redisUtil.expire(RedisKeys.TOKENS + game.getId(),expire);

            //奖品策略配置信息
            List<CardGameRules> rules = gameRulesService.listByMap(queryMap);
            //遍历策略,存入redis hset
            rules.forEach(r -> {
                redisUtil.hset(RedisKeys.MAXGOAL +game.getId(),r.getUserlevel()+"",r.getGoalTimes());
                redisUtil.hset(RedisKeys.MAXENTER +game.getId(),r.getUserlevel()+"",r.getEnterTimes());
                redisUtil.hset(RedisKeys.RANDOMRATE +game.getId(),r.getUserlevel()+"",r.getRandomRate());
            });
            redisUtil.expire(RedisKeys.MAXGOAL +game.getId(),expire);
            redisUtil.expire(RedisKeys.MAXENTER +game.getId(),expire);
            redisUtil.expire(RedisKeys.RANDOMRATE +game.getId(),expire);


            //活动状态变更为已预热,禁止管理后台再随便变动
            game.setStatus(1);
            gameService.updateById(game);

        });
    }

                                

        

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值