缓存是在第一次加载的数据进行复用,将数据存放指定地点以便下次加载使用。可防止多访问同一 数据库 而产生的堵塞,也能减轻 数据库 的压力!
Java缓存
原因
原有缓存失效(或者未加载到缓存中),因此 访问过程会跨越缓存直接访问 数据库,这一过程很有可能会导致 数据库 宕机(CPU、内存 高负载)
解决方案
原有缓存失效后,可通过 加锁 或 队列 进行控制 数据库的线程数量。失效期间 尽快修复 缓存,否则 用户访问会堵塞
加锁
public Users getByUsers(Long id) { // 1.先查询redis String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()+ "-id:" + id; String userJson = redisService.getString(key); if (!StringUtils.isEmpty(userJson)) { Users users = JSONObject.parseObject(userJson, Users.class); return users; } Users user = null; try { lock.lock(); // 查询db user = userMapper.getUser(id); redisService.setSet(key, JSONObject.toJSONString(user)); } catch (Exception e) { } finally { lock.unlock(); // 释放锁 } return user; }
原因
当用户查询指定数据 且 数据库 中恰好也没有,此时缓存自然也不会有。这样就导致 重复查询 都会访问数据库,类似的查询都会绕过缓存直接查数据库!
解决方案
- 如果首次 数据库 访问查询的数据为空,可直接设置一个默认值进行缓存,这样下次有相同的访问,就不会继续访问 数据库
- 也可把空结果,进行缓存,这样下次有相同的访问,就不会继续访问 数据库
public String getByUsers2(Long id) { // 1.先查询redis String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace() [1].getMethodName()+ "-id:" + id; String userName = redisService.getString(key); if (!StringUtils.isEmpty(userName)) { return userName; } System.out.println("######开始发送数据库DB请求########"); Users user = userMapper.getUser(id); String value = null; if (user == null) { // 标识为null value = ""; } else { value = user.getName(); } redisService.setString(key, value); return value; }
原因
在某一时间点 突然以超高并发进行访问 过期的key,导致 缓存被击穿
突然高频访问的key 称为 热点数据
解决方案
- 使用锁,单机
synchronized
、lock
等,分布式用 分布式锁- 缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存
原因
通过setnx命令,创建的key-value值,若未设置有效期;或者是持有锁的客户端崩溃未释放锁。都很有可能会卡住
解决方案
先
GET
命令 查看锁是否超时,超时则可使用getset
命令 获取锁