目录
1.缓存空对象方案实现逻辑,此处用查询店铺作为例子进行分享:
一.缓存穿透
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
常见的解决方案有两种:
- 缓存空对象
- 优点:实现简单,维护方便
- 缺点:额外的内存消耗,可能造成短期的不一致
- 布隆过滤器
- 优点:内存占用较少,没有多余key
- 缺点:实现复杂,存在误判可能
上面的方法是以被动的解决缓存穿透的问题,分享几点主动的解决方案:
- 增强id的复杂度,避免被猜测id的规律
- 做好数据的基础格式校验
- 加强用户权限校验
- 对热点参数做限流
1.缓存空对象方案实现逻辑,此处用查询店铺作为例子进行分享:
2. 实现代码:
@Override
public Result queryShopById(Long id) {
String shopKey = RedisConstants.CACHE_SHOP_KEY + id;
//1.先查询Redis缓中有没有数据
String s = stringRedisTemplate.opsForValue().get(shopKey);
Shop shop = new Shop();
//Redis不为空返回
if (StrUtil.isNotBlank(s)) {
shop = JSONUtil.toBean(s, Shop.class);
return Result.ok(shop);
}
//判断命中是否是空值
if (s == "") {
Result.fail("数据为空!");
}
//2.Redis中没有数据查询数据库中的数据
shop = shopMapper.selectById(id);
if (null != shop) {
//存在写入Redis
stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);
return Result.ok(shop);
}
//缓存空值,并设置过期时间
if (null == shop) {
stringRedisTemplate.opsForValue().set(shopKey, "", 2L, TimeUnit.MINUTES);
}
return Result.fail("数据不存在");
}
二.缓存雪崩:
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
- 给不同的key的TTL添加随机值 (添加预热数据时随机添加超时时间)
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存
三.缓存击穿:
缓存击穿问题也叫热点问题,就是一个被高并发访问并且缓存重建业务比较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种:
- 互斥锁
- 逻辑过期
1.互斥锁和逻辑过期
3.互斥锁和逻辑过期对比:
解决方案 | 优点 | 缺点 |
---|---|---|
互斥锁 |
|
|
逻辑过期 |
|
|
4.基于互斥锁方式解决缓存击穿问题
5.代码实现:
@Override
public Result queryShopById(Long id) {
String shopKey = RedisConstants.CACHE_SHOP_KEY + id;
//1.先查询Redis缓中有没有数据
String s = stringRedisTemplate.opsForValue().get(shopKey);
Shop shop = new Shop();
//Redis不为空返回
if (StrUtil.isNotBlank(s)) {
shop = JSONUtil.toBean(s, Shop.class);
return Result.ok(shop);
}
//判断命中是否是空值
if (s == "") {
Result.fail("数据为空!");
}
String lockKey = "lock:shop" + id;
try {
//如果为null去获取锁
if (!getLock(lockKey)) {
Thread.sleep(50); //休眠一段时间
queryShopById(id); //继续请求
}
//2.Redis中没有数据查询数据库中的数据
shop = shopMapper.selectById(id);
if (null != shop) {
//存在写入Redis
stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop), 30L, TimeUnit.MINUTES);
return Result.ok(shop);
}
//缓存空值,并设置过期时间
if (null == shop) {
stringRedisTemplate.opsForValue().set(shopKey, "", 2L, TimeUnit.MINUTES);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
delLock(lockKey);
}
return Result.fail("数据不存在!");
}
//获取锁
private Boolean getLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.MINUTES);
return BooleanUtil.isTrue(flag);
}
//释放锁
private void delLock(String key) {
stringRedisTemplate.delete(key);
}
6.基于逻辑过期解决缓存击穿问题(流程图)
代码展示:
//线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
@Override
public Result queryShopById(Long id) {
//验证判断是否存在缓存数据
Result date = getDate(id);
if (date != null) {
return date;
}
//过期,尝试获取互斥锁
String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
Boolean lock = getLock(lockKey);
//判断是否获取锁成功!
if (lock) {
//验证判断是否存在缓存数据
date = getDate(id);
if (date != null) {
return date;
}
//开启独立线程,缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
this.saveShop2Redis(id, 20L);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
delLock(lockKey);
}
});
//验证判断是否存在缓存数据
date = getDate(id);
if (date != null) {
return date;
}
}
return Result.fail("数据为空");
}
private Result getDate(Long id) {
String shopKey = RedisConstants.CACHE_SHOP_KEY + id;
//1.先查询Redis缓中有没有数据
String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
//Redis不为空返回
if (StrUtil.isEmpty(shopJson)) {
return Result.fail("数据为空!");
}
RedisData redisDataJson = JSONUtil.toBean(shopJson, RedisData.class);
JSONObject data = (JSONObject) redisDataJson.getData();
Shop shop = JSONUtil.toBean(data, Shop.class);
LocalDateTime expireTime = redisDataJson.getExpireTime();
//判断是否过期
if (expireTime.isAfter(LocalDateTime.now())) {
//未过期,返回旧的店铺信息
return Result.ok(shop);
}
return null;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/105065.html