大创项目的每一天一记——redis
业务问题
缓存穿透
查询不存在的数据,直接打击存储层
缓存雪崩
设置缓存 key 时候 集体过期,而导致数据库压力大
缓存击穿
热点数据,突然失效,打击数据库
解决办法
- 空结果缓存,解决缓存穿透
- 设置过期时间,解决缓存雪崩
- 加锁,解决缓存击穿
代码解决
对于 数据库 和 缓存redis中 调用的 临界区问题的探究,其中 读取应该使用两次,防止在临界区中 存在 多次查找数据库的问题,同时设置过期时间,随机数的过期时间。
同时 数据库 查询不到的时候,返回null 还设置了空值,防止数据库被缓存击穿问题。
@RequestMapping("/info/{dishId}")
public R info(@PathVariable("dishId") Long dishId){
// 缓存加锁实现 高并发请求
if (redisUtil.hasKey("menu_" + dishId)) {
return R.ok().put("dish", (DishEntity)redisUtil.get("menu_" + dishId));
}
// 查询数据库 和 放入 缓存要放在同一个临界区内,不然 在放入缓存的空间 可能导致高并发问题
synchronized (this) {
if (redisUtil.hasKey("menu_" + dishId)) {
return R.ok().put("dish", (DishEntity)redisUtil.get("menu_" + dishId));
}
DishEntity dish = dishService.getById(dishId);
redisUtil.set("menu_" + dishId, dish, (long)(Math.random() * 30));
return R.ok().put("dish", dish);
}
}
- 这时候考虑一个问题,假如微服务查询很多, 本地锁 如上, 不能够 将不同进程的 微服务进行 限制,还是大量的打在了 数据库上的话,也可能雪崩等问题,所以需要分布式锁进行设计防止。
上面是使用了 本地锁 进行设计 ,但是对于分布式锁 来说的 话,锁的对象 应该就是 换成 redis 进行 设计了,如下:
@RequestMapping("/info/{dishId}")
public R info(@PathVariable("dishId") Long dishId) throws InterruptedException {
// 本地分布式锁 实现
// 缓存加锁实现 高并发请求
if (redisUtil.hasKey("menu_" + dishId)) {
return R.ok().put("dish", (DishEntity)redisUtil.get("menu_" + dishId));
}
// 查询数据库 和 放入 缓存要放在同一个临界区内,不然 在放入缓存的空间 可能导致高并发问题
synchronized (this) {
if (redisUtil.hasKey("menu_" + dishId)) {
return R.ok().put("dish", (DishEntity)redisUtil.get("menu_" + dishId));
}
String uuid = UUID.randomUUID().toString();
// 设置过期,防止 redis 宕机导致 死锁了,服务错误
Boolean lock = redisTemplate.opsForValue().setIfAbsent("menu_lock", uuid, 3, TimeUnit.SECONDS);
if (lock) {
DishEntity dish;
try {
// 业务代码
dish = dishService.getById(dishId);
redisUtil.set("menu_" + dishId, dish, (long) (Math.random() * 30));
} finally {
// 删除锁
// if (uuid.equals(redisTemplate.opsForValue().get("menu_lock"))) {
// // 中间 判断 和 删除 操作 必须是原子操作,所以必须使用 脚本lua进行设计
// // 可能导致 在 判断 和 删除 中间因为延时而导致了 删除了 原本不属于自己的 锁,发生多线程下的操作
// redisTemplate.delete("menu_lock");
// }
// 使用脚本进行 redis 锁的删除 实现原子操作
String script = "if redis.call('get', KEYS[1]) == ARGV[1]"
+ "then return redis.call('del', KEYS[1])"
+ "else return 0 end";
redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList("menu_lock"), uuid);
}
// 返回结果
return R.ok().put("dish", dish);
} else {
// 进入自旋 查询状态 等待 100ms 重试
Thread.sleep(200);
return info(dishId);
}
}
}