MST

星途 面试题库

面试题:缓存设计之高级难度:缓存雪崩应对策略

假设系统中大量缓存同时失效,引发了缓存雪崩问题,你会从哪些方面设计应对策略来保障系统的稳定性和可用性?请详细说明。
49.5万 热度难度
后端开发缓存设计

知识考点

AI 面试

面试题答案

一键面试
  1. 缓存失效时间打散
    • 策略:为缓存设置不同的过期时间,避免大量缓存同时过期。例如,可以在原有的过期时间基础上,加上一个随机值(如1 - 10分钟的随机数)。这样,缓存过期时间就会分散开来,不会集中失效,从而降低缓存雪崩的风险。
    • 示例代码(以Python和Redis为例)
import redis
import random
r = redis.Redis(host='localhost', port=6379, db = 0)
key = 'example_key'
value = 'example_value'
base_expire = 3600  # 基础过期时间3600秒(1小时)
random_expire = random.randint(60, 600)  # 1 - 10分钟的随机数
total_expire = base_expire + random_expire
r.setex(key, total_expire, value)
  1. 使用互斥锁(Mutex)
    • 策略:在缓存失效后,当第一个请求来重建缓存时,使用互斥锁(如Redis的SETNX命令)来保证只有一个线程去重建缓存,其他线程等待。当重建完成后,释放锁,其他线程可以直接从缓存中获取数据。这样可以防止大量请求同时去重建缓存,从而避免对后端数据库等数据源造成过大压力。
    • 示例代码(以Python和Redis为例)
import redis
r = redis.Redis(host='localhost', port=6379, db = 0)
key = 'example_key'
mutex_key = 'example_mutex'
while True:
    value = r.get(key)
    if value:
        break
    if r.setnx(mutex_key, 1):
        try:
            # 重建缓存逻辑,例如从数据库读取数据
            new_value = 'new_value_from_db'
            r.setex(key, 3600, new_value)
        finally:
            r.delete(mutex_key)
        break
    else:
        # 等待一段时间后重试
        import time
        time.sleep(0.1)
  1. 二级缓存
    • 策略:采用二级缓存架构,例如设置一个本地缓存(如Guava Cache)作为一级缓存,Redis作为二级缓存。当请求到达时,先从本地缓存获取数据,如果本地缓存没有命中,再从Redis获取。如果Redis也没有命中,才去后端数据源获取数据,并依次更新两级缓存。这样即使Redis中的大量缓存失效,本地缓存还能在一定程度上提供数据,减轻后端压力。
    • 示例代码(以Java和Guava Cache、Redis为例)
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import redis.clients.jedis.Jedis;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class DoubleCacheExample {
    private static LoadingCache<String, String> localCache = CacheBuilder.newBuilder()
           .maximumSize(1000)
           .expireAfterWrite(10, TimeUnit.MINUTES)
           .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return null;
                }
            });
    private static Jedis jedis = new Jedis("localhost", 6379);

    public static String getValue(String key) {
        String value = localCache.getIfPresent(key);
        if (value == null) {
            value = jedis.get(key);
            if (value != null) {
                localCache.put(key, value);
            } else {
                // 从后端数据源获取数据
                value = "value_from_backend";
                jedis.setex(key, 3600, value);
                localCache.put(key, value);
            }
        }
        return value;
    }
}
  1. 缓存持久化
    • 策略:对于Redis等缓存系统,启用持久化机制(如RDB或AOF)。这样在系统重启时,可以快速恢复缓存数据,避免缓存全部失效的情况。RDB会在指定的时间间隔内将内存中的数据集快照写入磁盘,AOF则是将每次写操作追加到文件末尾。可以根据实际需求选择合适的持久化方式或两者结合使用。
    • 配置示例
      • RDB配置:在redis.conf文件中,设置save 900 1表示900秒内如果有1个键被修改,则进行RDB快照。
      • AOF配置:在redis.conf文件中,设置appendonly yes开启AOF持久化,并可以设置appendfsync everysec表示每秒将缓冲区内容写入AOF文件。
  2. 服务降级与熔断
    • 策略:当缓存雪崩发生,系统压力过大时,启用服务降级策略。例如对于一些非核心业务,可以直接返回默认值或者提示信息,避免占用过多资源。同时,结合熔断机制,当对后端数据源的请求失败率达到一定阈值时,暂时切断对数据源的请求,直接返回降级结果,防止对数据源的进一步压力,待系统恢复稳定后再尝试恢复正常请求。
    • 示例(以Hystrix框架为例)
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class FallbackCommand extends HystrixCommand<String> {
    private final String key;

    public FallbackCommand(String key) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.key = key;
    }

    @Override
    protected String run() throws Exception {
        // 正常从数据源获取数据逻辑
        return "data_from_backend";
    }

    @Override
    protected String getFallback() {
        // 降级逻辑,返回默认值
        return "default_value";
    }
}
  1. 增加缓存副本
    • 策略:对一些重要的缓存数据,创建多个副本,并将这些副本分布在不同的缓存节点上。当某个节点的缓存失效时,其他节点的副本可以继续提供服务,从而提高系统的可用性。可以通过一致性哈希算法等方式来管理缓存副本的分布。
    • 示例:在使用Redis Cluster时,可以通过配置副本数量来实现缓存副本。例如在集群配置文件中设置cluster - replicas 1表示每个主节点有1个副本节点。这样当主节点缓存失效时,副本节点可以继续提供服务。