Redis 面试

警告
本文最后更新于 2022-10-11,文中内容可能已过时。

使用缓存,一般是数据库中存储一份,缓存中复制一份。当收到请求,先从缓存查询数据,如果存在数据,直接返回缓存中的结果。如果缓存中没有数据,则去数据库中查询数据,同时复制到缓存中,然后返回结果。如果数据库中也没有数据,直接返回空。

缓存穿透

  • 原因:大量用户请求在缓存和数据库中都不存在的数据。
  • 结果:所有的无效请求都会直接落到数据库上,导致数据库压力增大。
  • 解决:
    1. 进行合法校验。可以有效拦截大部分不合法的请求。
    2. 使用布隆过滤器。不在布隆过滤器中的数据一定不存在,对于频繁更新的数据不适用。
    3. 对无效的请求也进行缓存,只是过期时间设置得较短,一般五分钟内。

缓存击穿

  • 原因:某数据的缓存突然失效,大量用户请求该数据。
  • 结果:大量请求直接落到数据库上,导致数据库压力增大。
  • 解决:
    1. 如果是热点数据,可以考虑设置永远不过期。
    2. 当数据过期后,设置一个互斥锁,只让一个请求通过,去数据库查询数据并更新缓存。不管结果如何都需要释放锁,否则其他线程会一直等待。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static String getProductDescById(String id) {
    String desc = null;
    int count = 0;            // 自旋次数
    while (count < 5) {       // 设置最大自旋次数,减少资源占用
        desc = redis.get(id); // 查询缓存
        if (desc == null) {   // 缓存为空,已过期
            // 互斥锁,只有一个请求可以成功
            if (redis.setnx(lock_id, 1, 60) == 1) {
                try {
                    desc = getFromDB(id);              // 查询数据库
                    redis.set(id, desc, 60 * 60 * 24); // 更新缓存
                } catch (Exception e) {
                    LogHelper.error(e);
                } finally {
                    redis.del(lock_id); // 必须释放锁
                    return desc;
                }
            } else {
                Thread.sleep(200); // 200ms 自旋一次
                count++;
            }
        } else {
            break;
        }
    }
    return desc;
}

缓存雪崩

  • 原因:大量缓存同时或短时间内失效。
  • 结果:大量请求直接落到数据库上,导致数据库压力增大。
  • 解决:
    1. 如果是热点数据,可以考虑设置永远不过期。
    2. 缓存的过期时间可以考虑设置一定范围内的随机值,有效防止大量缓存同时过期。
    3. 设置 Redis 服务器集群。
    4. 双缓存。A 设置过期时间,B 不设置过期时间,当 A 中缓存失效时查询 B,同时更新 A,但是更新数据库时需要同步更新两个缓存。
1
redis.set(id, value, 60 * 60 + Math.random() * 1000); // 设置一定范围内的随机值

锁的作用是保证多个进程或线程在并发操作共享资源时资源的正确性。在分布式应用中,一个服务需要部署多个实例,对于操作分布式环境下的共享资源,就需要使用分布式锁来保证操作的正确性。

分布式锁应该具有互斥、可重入、锁超时,高可用等特点。其中前面几个特点和本地锁具体的特点相同,高可用是分布式锁需要具备的重要的特点。

单节点实现

  • 加锁过程要保证原子性。
  • 保证谁加的锁只能被谁解锁。
  • 设置锁超时时间,防止加锁方异常无法释放锁时其他客户端无法获取锁,同时,超时时间要大于业务处理时间。

多节点实现

  • RedLock 算法:让客户端和多个独立的Redis实例依次请求加锁,如果能和半数以上的实例加锁成功,就可认为客户端获取分布式锁成功。