缓存Redis
当前的电商平台访问量高,导致请求常常超时
为了提升系统响应和用户体验,防止大量请求直接落到SQL服务器
选择Redis分布式缓存
这里需要注意:
- 缓存击穿
- 缓存雪崩
- 缓存穿透
讨论了对应不同场景的各类解决方案,并将分布式锁和本地锁进行对比
最终Redis展示如下:
缓存穿透
/*
对于缓存穿透,如果catalogJson为null,可以设置stringRedisTemplate.opsForValue().set("catalogJSON", null);
*/
缓存雪崩
/*
randomTime = Math.Random(0,1)
对于缓存雪崩,可以设置stringRedisTemplate.opsForValue().set("catalogJSON", s, randomTime, TimeUnit.DAYS);
*/
public Map<String, List<Catelog2Vo>> getCatelogJson2() {
//加入缓存逻辑,缓存中存的数据是JSON字符串
//JSON跨语言,跨平台兼容
//从缓存中取出的数据要逆转为能用的对象类型,序列化与发序列化
String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
if (StringUtils.isEmpty(catalogJSON)) {
//缓存中没有数据,查询数据库
//这里使用了redis锁,先锁住
Map<String, List<Catelog2Vo>> catelogJsonFromDb = getCatelogJsonFromDbWithRedisLock();
//查到的数据再放入缓存,将对象转为JSON放入缓存中
String s = JSON.toJSONString(catelogJsonFromDb);
/*
对于缓存穿透,如果catalogJson为null,可以设置stringRedisTemplate.opsForValue().set("catalogJSON", null);
*/
/*
randomTime = Math.Random(0,1)
对于缓存击穿,可以设置stringRedisTemplate.opsForValue().set("catalogJSON", s, randomTime, TimeUnit.DAYS);
*/
stringRedisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
return catelogJsonFromDb;
}
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return result;
}
缓存击穿
本地进程锁
//本地进程锁
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithLocalLock() {
synchronized (this) {
String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
if (StringUtils.isEmpty(catalogJSON)) {
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return result;
}
}
/**
* 优化:将数据库中的多次查询变为一次,存至缓存selectList,需要的数据从list取出,避免频繁的数据库交互
*/
List<CategoryEntity> selectList = baseMapper.selectList(null);
//1.查出所有1级分类
List<CategoryEntity> level1 = getParent_cid(selectList, 0L);
//2.封装数据
Map<String, List<Catelog2Vo>> parent_cid = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//1.查出1级分类中所有2级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
//2.封装上面的结果
List<Catelog2Vo> catelog2Vos = null;
if (categoryEntities != null) {
catelog2Vos = categoryEntities.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
//查询当前2级分类的3级分类
List<CategoryEntity> level3 = getParent_cid(selectList, l2.getCatId());
if (level3 != null) {
List<Catelog2Vo.Catelog3Vo> collect = level3.stream().map(l3 -> {
//封装指定格式
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catelog3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(collect);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}
));
String s = JSON.toJSONString(parent_cid);
stringRedisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
return parent_cid;
}
分布式程锁Redis
//分布式程锁Redis
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock() {
//占分布式锁,同时设置锁过期时间,必须和加锁同步原子操作
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111", 300, TimeUnit.SECONDS);
if (lock) {
//加锁成功,执行业务
Map<String, List<Catelog2Vo>> dataFromDB;
try {
dataFromDB = getDataFromDB();
} finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1])else return 0 end";
//删除锁,Lua脚本
Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock", uuid));
}
return dataFromDB;
} else {
//加锁失败,休眠2秒,重试
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
return getCatelogJsonFromDbWithRedisLock();//自旋
}
}
//分布式锁Redisson
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedissonLock() {
RLock lock = redisson.getLock("catalogJson-lock");
lock.lock();
//加锁成功,执行业务
Map<String, List<Catelog2Vo>> dataFromDB;
try {
dataFromDB = getDataFromDB();
} finally {
lock.unlock();
}
return dataFromDB;
}