缓存的使用场景
- 数据实时性要求不高
- 数据访问量大且更新频率不高的数据
经常使用的缓存技术
-
非关系型数据缓存 CDN:内容分发网络
Nginx:静态资源服务器 -
关系型数据缓存 guavacache:谷歌开源的本地缓存组件
caffine:第三方开源本地缓存组件
redis:分布式缓存中间件
互联网应用缓存架构图
缓存的击穿、穿透及雪崩
-
缓存击穿: 对于一些设置了过期时间的Key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常热点的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
解决:加锁,大量并发只让一个请求去查询热key,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去查db -
缓存穿透: 定义:指定查询一个一定不存在的数据,由于缓存是不命中的,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
解决:插空值,并加入短暂过期时间 -
缓存雪崩: 缓存雪崩:缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB顺势压力过重雪崩。
原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样可以将缓存时间打散,不在同一时间失效。
分布式环境下的缓存击穿问题解决
- 使用分布式锁锁住数据库查询插入缓存逻辑
分布式锁的实现
public Object getDistributeLock() {
String uuid = UUID.randomUUID().toString();
if(lock("lock", uuid)) {
...
// 查询数据库处理
...
unlock("lock", uuid)
}
}
public boolean lock(String lock, String uuid) {
// 问题一:死锁问题,如果程序执行失败,Redis锁未得到释放就会导致死锁问题
// 问题解决:给锁设置一个过期时间,并且保证原子性
return redisTemplate.opsForValue().setIfAbsent(lock, uuid, 300,
}
public void unlock(String lock, String uuid) {
// 问题二:查询数据库处理超过redis锁过期时间,别的线程进来,第一个线程将正在处理线程的锁删了
// 问题解决:redis的值设置成唯一的,比如uuid
// 问题三:对比锁值和删除锁的操作不是原子操作,程序闪断会导致锁没有被正常删除
// 问题解决:使用redis脚本替代一下处理逻辑
// if redis.call("get", KEYS[1]) == ARGV[1]
// then
// return redis.call("del", KEYS[1])
// else
// return 0
// end
if (uuid.equals(lockValue)) {
redisTemplate.delete(lock);
}
return object;
}
分布式锁的第三方组件:Redisson
如果要通过分布式的方式实现所有的本地加锁API,那么我们不用全部手动实现,可以用第三方组件Redisson来实现,Redisson中实现了所有本地锁的分布式版本。
- redis的Maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
- 配置
// 默认连接地址 127.0.0.1:6379
RedissonClient redisson = Redisson.create();
Config config = new Config();
// redis://127.0.0.1:7181
// 可以用"rediss://"来启用 SSL 连接
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
RedissonClient redisson = Redisson.create(config);
- 使用分布式锁
RLock lock = redisson.getLock("anyLock");// 最常见的使用方法
lock.lock();
// 加锁以后 10 秒钟自动解锁// 无需调用 unlock 方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待 100 秒,上锁以后 10 秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}