商户查询缓存
把最近查询过的商户数据存储在Redis中,如果再有人查询先从Redis中获取,没有的话再从MySQL数据库中获取,然后存入Redis
商户种类列表也要
Redis的缓存更新策略
需要注意@Transactional只是对应单一关系型数据库的事务一致性,如果想要操作多个数据库或者非关系型数据库需要用别的方法保证一致性
缓存穿透问题
客户端请求的数据在数据库和缓存中都没有,请求就会一直打到数据库,缓存失效
如果有恶意线程高并发的一直请求不存在的数据,会对数据库造成巨大压力
缓存空对象
把请求空的值放在缓存中,实现简单维护方便,但是会有额外内存消耗(可以通过设置一个小的TTL缓解),可能会有短期的数据不一致
需要注意实现这种方案的时候,在redis中查到数据还要额外再判断是否为空
布隆过滤
在redis和客户端之间加一个布隆过滤器,请求先经过布隆过滤,存在则放行,不存在则拒绝
原理:可以简单地看成是存储二进制位的哈希表,数据库有变更的话就会改变其哈希表的二进制位,数据库每个id通过一定的哈希算法映射到布隆过滤器
得到的结果是“可能在”,但在的元素绝对不会报告“不存在”
m是数组大小
使用布隆过滤器的方案:黑马点评改进1.布隆过滤器_黑马点评布隆过滤器-CSDN博客
实际应用中可以用布隆过滤器加缓存空对象,把从布隆过滤器漏过的不存在的对象创建空对象再加到缓存
其他的一些主动方案
增强id的复杂度 + 格式校验
用户权限校验
热点数据限流
缓存雪崩问题
同一时间有大量key缓存同时失效或者Redis服务宕机,导致大量请求到达数据库造成巨大压力
解决方案
1.给不同key的TTL添加随机变动值
2.构建Redis集群提高服务的可用性(主从 + 哨兵)
3.给缓存业务添加降级限流策略(微服务)
4.给业务添加多级缓存(ngnix + redis + JVM)
缓存击穿问题
也叫热点key问题,一个高并发且重建缓存较为复杂的key突然失效,大量请求到达数据库
互斥锁
死锁情况:有不同线程要查询不同的Redis缓存,互相持有对方Redis缓存的锁
正好在string的操作方法里,setnx也有这个功能,这样在高并发下只有第一个成功设置的线程才能更新缓存(和分布式锁的思想类似)
但是要考虑到线程崩溃的情况,如果崩溃了就永远不会有人来释放锁,所以锁字段要添加一个TTL
boolean locked = false;
try{
//如果未命中,判断能否获得互斥锁,拿不到则休眠一段时间再获取
while(!(locked = tryLock(id))){
Thread.sleep(50);
s = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id.toString());
if(!StringUtils.isBlank(s)){
Shop shop = JSONUtil.toBean(s, Shop.class);
stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY + id.toString(), RedisConstants.CACHE_SHOP_TTL, TimeUnit.HOURS);
return Result.ok(shop);
}
}
s = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id.toString());
if(!StringUtils.isBlank(s)){
Shop shop = JSONUtil.toBean(s, Shop.class);
stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY + id.toString(), RedisConstants.CACHE_SHOP_TTL, TimeUnit.HOURS);
return Result.ok(shop);
}
Thread.sleep(5000);
log.debug("用数据库查询商户");
Shop shop = getById(id);
if(shop == null){
return Result.fail("店铺信息不存在");
}
String json = JSONUtil.toJsonStr(shop);
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id.toString(), json);
stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_KEY + id.toString(), RedisConstants.CACHE_SHOP_TTL, TimeUnit.HOURS);
return Result.ok(shop);
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
if(locked){
freeLock(id);
log.debug("释放了锁");
}
}
逻辑过期
不为字段设置一个TTL,而是在JSON串中加入一个逻辑上的TTL,从Redis中获取到之后与现在的时间比对判断是否过期
如果过期的话就获取锁,新建一个线程(线程池中获取)更新缓存中的对象,其他的请求暂时返回过期的数据
如何在原有的Shop上加入逻辑过期时间而不影响原类:创建一个新的RedisData类,让其带有一个Object属性和一个time属性,注意通过toBean方法解析RedisData类之后的Object对象会被转换成JSONObject对象,再用toBean解析一遍即可
缓存工具封装
针对上面的问题和解决方案创建一个单独的工具类封装方法
注意其中泛型推断的逻辑