前言:
? ? ? ? 高并發的活動預熱肯定不可以在數據庫操作,需要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());//有相關活動數據,則將活動數據預熱,進redislist.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 hsetrules.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);});}
????????????????????????????????
????????
????????