Redis缓存穿透,通常是有人大量访问未知的URL,导致后端穿透Redis,直接访问数据库,这里采用在Redis中注入空key来解决;
Redis缓存击穿,通常为Redis热点key失效问题,导致大量请求同时访问这个key,转而访问数据库,这里采用Redis读延期以及DCl做数据库读取解决;
Redis缓存雪崩,通常为Redis大量key同时失效导致,解决方案,生成随机过期时间,防止同时失效。
//本地缓存map
private static Map<String,GoodsVo> goodsVoMap=new HashMap<>();
//这个map如果商品过多,有可能超出内存OOM,其次还有分布式缓存一致性问题,这里是本地Map
//也许能通过ConcurrentHashMap实现LRU来解决
@RequestMapping(value = "/detail/{goodsId}")
@ResponseBody
public RespBean toDetail(HttpServletRequest request, HttpServletResponse response, Model model, User user, @PathVariable Long goodsId) {
ValueOperations valueOperations = redisTemplate.opsForValue();
GoodsVo goods = getGoodsFromCache(goodsId);
if (goods == null) {
//DCL
boolean lock = valueOperations.setIfAbsent("lock:goodsId:" + goodsId, goodsId, 1, TimeUnit.SECONDS);
try {
if (lock) {
goods = getGoodsFromCache(goodsId);
if (goods == null) {
goods = goodsService.findGoodsVoByGoodsId(goodsId);
//数据库包含数据
if (goods != null) {
valueOperations.set("goodsDetail:" + goodsId, JsonUtil.object2JsonStr(goods), genGoodsCacheTimeout(), TimeUnit.SECONDS);
goodsVoMap.put("goodsDetail:" + goodsId,goods);
} else {
//数据库为空,放入空值
valueOperations.set("goodsDetail:" + goodsId, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
}
}
}
} finally {
redisTemplate.delete("lock:goodsId:" + goodsId);
}
}
Date startDate = goods.getStartDate();
Date endDate = goods.getEndDate();
Date nowDate = new Date();
//秒杀状态
int secKillStatus = 0;
//剩余开始时间
int remainSeconds = 0;
if (nowDate.before(startDate)) {
remainSeconds = (int) ((startDate.getTime() - nowDate.getTime()) / 1000);
} else if (nowDate.after(endDate)) {
secKillStatus = 2;
remainSeconds = -1;
// 秒杀中
} else {
secKillStatus = 1;
remainSeconds = 0;
}
DetailVo detailVo = new DetailVo();
detailVo.setGoodsVo(goods);
detailVo.setUser(user);
detailVo.setRemainSeconds(remainSeconds);
detailVo.setSecKillStatus(secKillStatus);
return RespBean.success(detailVo);
}
private GoodsVo getGoodsFromCache(Long goodsId) {
GoodsVo goods = null;
//jvm进程级别缓存,实际就是内存
//单机内存比redis更高,可以达到百万级并发
goods=goodsVoMap.get("goodsDetail:" + goodsId);
if(goods!=null){
return goods;
}
ValueOperations valueOperations = redisTemplate.opsForValue();
String goodsDetail = (String) valueOperations.get("goodsDetail:" + goodsId);
if (!StringUtils.isEmpty(goodsDetail)) {
if (EMPTY_CACHE.equals(goodsDetail)) {
//空值返回错误
redisTemplate.expire("goodsDetail:" + goodsId, genEmptyCacheTimeout(), TimeUnit.SECONDS);
return new GoodsVo();
}
goods = JsonUtil.jsonStr2Object(goodsDetail, GoodsVo.class);
//读延期
redisTemplate.expire("goodsDetail:" + goodsId, genGoodsCacheTimeout(), TimeUnit.SECONDS);
}
return goods;
}
private Integer genGoodsCacheTimeout() {
return 60 * 60 * 24 + new Random().nextInt(5) * 60 * 60;
}
private Integer genEmptyCacheTimeout() {
return 60 + new Random().nextInt(30);
}