網(wǎng)站的網(wǎng)站地圖怎么做seo推廣外包
緩存預(yù)熱
為什么要做預(yù)熱:
當(dāng)活動(dòng)真正開(kāi)始時(shí),需要超高的并發(fā)訪問(wèn)活動(dòng)相關(guān)信息 必須把必要的數(shù)據(jù)提前加載進(jìn)redis
預(yù)熱的策略:
在msg中寫一個(gè)定時(shí)任務(wù)
每分鐘掃描一遍card_game表
把(開(kāi)始時(shí)間?>?當(dāng)前時(shí)間)&&?(開(kāi)始時(shí)間?<=?當(dāng)前時(shí)間+1分鐘)的活動(dòng)及相關(guān)信息放入redis
緩存預(yù)熱流程:
???????代碼上需要取出滿足條件的活動(dòng)列表,對(duì)每個(gè)活動(dòng)查出相應(yīng)的獎(jiǎng)品放到令牌桶,查出相應(yīng)的活動(dòng)策略放到Redis
1、查詢1分鐘內(nèi)的活動(dòng)?
2、循環(huán)遍歷活動(dòng)列表,挨個(gè)處理,假設(shè)當(dāng)前取出的是A
3、查詢A相關(guān)的獎(jiǎng)品列表及數(shù)量?
4、根據(jù)總數(shù)量生成獎(jiǎng)品相關(guān)的令牌桶
5、查詢A相關(guān)的活動(dòng)策略:抽獎(jiǎng)次數(shù)、中獎(jiǎng)次數(shù)等,放入Redis
緩存體系
1)活動(dòng)基本信息?k-v,以活動(dòng)id為key,活動(dòng)對(duì)象為value,永不超時(shí)
redisUtil.set(RedisKeys.INFO+game.getId(),game,-1);
2)活動(dòng)策略信息
使用hset,以活動(dòng)id為group,用戶等級(jí)為key,策略值為value
redisUtil.hset(RedisKeys.MAXGOAL + game.getId(),r.getUserlevel()+"",r.getGoalTimes());
redisUtil.hset(RedisKeys.MAXENTER +
game.getId(),r.getUserlevel()+"",r.getEnterTimes());
3)抽獎(jiǎng)令牌桶?雙端隊(duì)列,以活動(dòng)id為key,在活動(dòng)時(shí)間段內(nèi),隨機(jī)生成時(shí)間戳做令牌,有多少個(gè)獎(jiǎng)品就生成多少個(gè)令牌。令牌即獎(jiǎng)品發(fā)放的時(shí)間點(diǎn)。從小到大排序后從右側(cè)入隊(duì)。
redisUtil.rightPushAll(RedisKeys.TOKENS + game.getId(),tokenList);
4)獎(jiǎng)品映射信息
k-v ,?以活動(dòng)id_令牌為key,獎(jiǎng)品信息為value,會(huì)員獲取到令牌后,如果令牌有效,則用令牌token值,來(lái)這里獲取 獎(jiǎng)品詳細(xì)信息
redisUtil.set(RedisKeys.TOKEN + game.getId() +"_"+token,cardProduct,expire);
5)令牌設(shè)計(jì)技巧?
假設(shè)活動(dòng)時(shí)間間隔太短,獎(jiǎng)品數(shù)量太多。那么極有可能產(chǎn)生的時(shí)間戳發(fā)生重復(fù)。
解決技巧:額外再附加一個(gè)隨機(jī)因子。將 (時(shí)間戳?* 1000 + 3位隨機(jī)數(shù))作為令牌。抽獎(jiǎng)時(shí),將抽中的令牌/1000?,還原真實(shí)的時(shí)間戳。
//活動(dòng)持續(xù)時(shí)間(ms)
long duration = end - start;
long rnd = start + new Random().nextInt((int)duration);
//為什么乘1000,再額外加一個(gè)隨機(jī)數(shù)呢? - 防止時(shí)間段獎(jiǎng)品多時(shí)重復(fù)
long token = rnd * 1000 + new Random().nextInt(999);
6)中獎(jiǎng)計(jì)數(shù)?k-v,以活動(dòng)id_用戶id作為key,中獎(jiǎng)數(shù)為value,利用redis原子性,中獎(jiǎng)后incr增加計(jì)數(shù)。 抽獎(jiǎng)次數(shù)計(jì)數(shù)也是同樣的道理
redisUtil.incr(RedisKeys.USERHIT+gameid+"_"+user.getId(),1);
?7)中獎(jiǎng)邏輯判斷 : 抽獎(jiǎng)時(shí),從令牌桶左側(cè)出隊(duì)和當(dāng)前時(shí)間比較,如果令牌時(shí)間戳小于等于當(dāng)前時(shí)間,令牌有效,表示中獎(jiǎng)。大于當(dāng)前 時(shí)間,則令牌無(wú)效,將令牌還回,從左側(cè)壓入隊(duì)列。
代碼開(kāi)發(fā)
代碼在msg項(xiàng)目下的GameTask里,已集成Spring調(diào)度?
看了下Spring調(diào)度 ,@Scheduled內(nèi)寫循環(huán)時(shí)間,下面的代碼會(huì)定時(shí)執(zhí)行
commons模塊下有個(gè)RedisKeys,已經(jīng)定義了可用的Redis key前綴,可以直接使用
接下來(lái)補(bǔ)全GameTask中的函數(shù)
先用QueryWrapper取到下一分鐘所有的任務(wù)
// 獲取當(dāng)前時(shí)間
Date now = new Date();// 查詢將來(lái)1分鐘內(nèi)要開(kāi)始的活動(dòng)
QueryWrapper<CardGame> gameQueryWrapper = new QueryWrapper<>();
// 開(kāi)始時(shí)間大于當(dāng)前時(shí)間
gameQueryWrapper.gt("starttime", now);
// 小于等于(當(dāng)前時(shí)間+1分鐘)
gameQueryWrapper.le("starttime", DateUtils.addMinutes(now, 1));List<CardGame> list = gameService.list(gameQueryWrapper);
if (list.isEmpty()) {// 沒(méi)有查到要開(kāi)始的活動(dòng)log.info("No upcoming games within the next minute.");
} else {log.info("Found {} upcoming games.", list.size());
}
對(duì)于每個(gè)任務(wù),獲取到要存入Redis中的信息
list.forEach(game -> {// 活動(dòng)開(kāi)始時(shí)間long start = game.getStarttime().getTime();// 活動(dòng)結(jié)束時(shí)間long end = game.getEndtime().getTime();// 計(jì)算活動(dòng)結(jié)束時(shí)間到現(xiàn)在還有多少秒,作為redis key過(guò)期時(shí)間long expire = (end - now.getTime()) / 1000;// 活動(dòng)持續(xù)時(shí)間(ms)long duration = end - start;// 創(chuàng)建查詢參數(shù)的MapMap<String, Object> queryMap = new HashMap<>();queryMap.put("gameid", game.getId());
先將基本信息存入Redis
// 活動(dòng)基本信息
game.setStatus(1);
redisUtil.set(RedisKeys.INFO + game.getId(), game, -1);
log.info("Loaded game info: {}, {}, {}, {}", game.getId(), game.getTitle(), game.getStarttime(), game.getEndtime());
把獎(jiǎng)品放入map
// 活動(dòng)獎(jiǎng)品信息
List<CardProductDto> products = gameLoadService.getByGameId(game.getId());
Map<Integer, CardProduct> productMap = new HashMap<>(products.size());
products.forEach(p -> productMap.put(p.getId(), p));
log.info("Loaded product types: {}", productMap.size());
//獎(jiǎng)品數(shù)量等配置信息
List<CardGameProduct> gameProducts = gameProductService.listByMap(queryMap);
log.info("load bind product:{}",gameProducts.size());
令牌桶創(chuàng)建,然后token存入Redis
// 令牌桶
List<Long> tokenList = new ArrayList<>();
gameProducts.forEach(cgp -> {// 生成amount個(gè)start到end之間的隨機(jī)時(shí)間戳做令牌for (int i = 0; i < cgp.getAmount(); i++) {long rnd = start + new Random().nextInt((int) duration);// 為什么乘1000,再額外加一個(gè)隨機(jī)數(shù)呢? - 防止時(shí)間段獎(jiǎng)品多時(shí)重復(fù)// 記得取令牌判斷時(shí)間時(shí),除以1000,還原真正的時(shí)間戳long token = rnd * 1000 + new Random().nextInt(999);// 將令牌放入令牌桶tokenList.add(token);// 以令牌做key,對(duì)應(yīng)的商品為value,創(chuàng)建redis緩存log.info("Token -> Game: {} -> {}", token / 1000, productMap.get(cgp.getProductid()).getName());// Token到實(shí)際獎(jiǎng)品之間建立映射關(guān)系redisUtil.set(RedisKeys.TOKEN + game.getId() + "_" + token, productMap.get(cgp.getProductid()), expire);}
});// 排序后放入redis隊(duì)列
Collections.sort(tokenList);
log.info("Loaded tokens: {}", tokenList);// 從右側(cè)壓入隊(duì)列,從左到右,時(shí)間戳逐個(gè)增大
redisUtil.rightPushAll(RedisKeys.TOKENS + game.getId(), tokenList);
redisUtil.expire(RedisKeys.TOKENS + game.getId(), expire);
接著將策略存入Redis
// 獎(jiǎng)品策略配置信息
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());log.info("Loaded rules: level={}, enter={}, goal={}, rate={}",r.getUserlevel(), r.getEnterTimes(), r.getGoalTimes(), r.getRandomRate());
});
redisUtil.expire(RedisKeys.MAXGOAL + game.getId(), expire);
redisUtil.expire(RedisKeys.MAXENTER + game.getId(), expire);
redisUtil.expire(RedisKeys.RANDOMRATE + game.getId(), expire);
寫完運(yùn)行發(fā)現(xiàn)每隔一分鐘嘗試將活動(dòng)信息寫入緩存
然后寫了個(gè)緩存接口來(lái)測(cè)試,代碼從Redis里取數(shù)據(jù)即可
@GetMapping("/info/{gameid}")
@ApiOperation(value = "緩存信息")
@ApiImplicitParams({@ApiImplicitParam(name="gameid",value = "活動(dòng)id",example = "1",required = true)
})
public ApiResult info(@PathVariable int gameid) {Map<String, Object> resMap = new LinkedHashMap<>();Map<String, Object> tokenMap = new LinkedHashMap();Object gameInfo = redisUtil.get(RedisKeys.INFO + gameid);Map<Object, Object> maxGoalMap = redisUtil.hmget(RedisKeys.MAXGOAL + gameid);Map<Object, Object> maxEnterMap = redisUtil.hmget(RedisKeys.MAXENTER + gameid);List<Object> tokenList = redisUtil.lrange(RedisKeys.TOKENS + gameid, 0, -1);resMap.put(RedisKeys.INFO + gameid, gameInfo);resMap.put(RedisKeys.MAXGOAL + gameid, maxGoalMap);resMap.put(RedisKeys.MAXENTER + gameid, maxEnterMap);for (Object item : tokenList) {Object tokenData = redisUtil.get(RedisKeys.TOKEN + gameid + "_" + item.toString());Long key = Long.valueOf(item.toString());Date date = new Date(key / 1000);SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");String formattedDate = dateFormat.format(date);tokenMap.put(formattedDate, tokenData);}resMap.put(RedisKeys.TOKENS + gameid, tokenMap);return new ApiResult(200, "緩存信息", resMap);
}
運(yùn)行測(cè)試
在后臺(tái)數(shù)據(jù)庫(kù)啟動(dòng)一個(gè)近期活動(dòng):
查看Redis