缓存碎碎念

187 阅读3分钟

缓存的使用场景

  • 数据实时性要求不高
  • 数据访问量大且更新频率不高的数据

经常使用的缓存技术

  • 非关系型数据缓存         CDN:内容分发网络
            Nginx:静态资源服务器

  • 关系型数据缓存         guavacache:谷歌开源的本地缓存组件
            caffine:第三方开源本地缓存组件
            redis:分布式缓存中间件

互联网应用缓存架构图

src=http___img.shangdixinxi.com_up_info_201910_20191024224701581344.png&refer=http___img.shangdixinxi.jfif

缓存的击穿、穿透及雪崩

  • 缓存击穿:         对于一些设置了过期时间的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中实现了所有本地锁的分布式版本。

  1. redis的Maven依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>
  1. 配置
// 默认连接地址 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);
  1. 使用分布式锁
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();
    }
}