MST

星途 面试题库

面试题:Redis缓存减轻MySQL读压力:缓存穿透应对

在使用Redis缓存减轻MySQL读压力场景中,若出现缓存穿透问题,你会采取哪些措施解决?请详细说明每种措施的原理和实现方式。
28.0万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试

1. 布隆过滤器(Bloom Filter)

  • 原理:布隆过滤器是一个很长的二进制向量和一系列随机映射函数。它可以判断一个元素一定不存在或者可能存在。当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这样可以在大量数据中快速判断某个数据是否存在,避免无效查询穿透到数据库。
  • 实现方式:在Java中,可以使用Google的Guava库来实现布隆过滤器。例如:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

// 初始化布隆过滤器,预计元素数量为1000000,误判率为0.01
BloomFilter<Integer> bloomFilter = BloomFilter.create(
        Funnels.integerFunnel(),
        1000000,
        0.01);

// 添加元素
bloomFilter.put(123);

// 判断元素是否存在
boolean mightContain = bloomFilter.mightContain(123);

在应用启动时,将数据库中的所有可能查询的键值加入布隆过滤器。每次查询前,先通过布隆过滤器判断该键是否可能存在,如果不存在则直接返回,不再查询Redis和数据库。

2. 缓存空值

  • 原理:当查询数据库发现某个键对应的值为空时,也将这个空值缓存到Redis中,并设置一个较短的过期时间。这样下次再查询同样的键时,直接从Redis中获取空值,避免穿透到数据库。
  • 实现方式:以Java的Jedis为例:
import redis.clients.jedis.Jedis;

public class CacheNullValueExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String key = "nonexistent_key";
        // 先尝试从Redis获取值
        String value = jedis.get(key);
        if (value == null) {
            // 查询数据库
            value = getFromDatabase(key);
            if (value != null) {
                // 数据库有值,缓存到Redis
                jedis.setex(key, 3600, value);
            } else {
                // 数据库无值,缓存空值,设置较短过期时间
                jedis.setex(key, 60, "");
            }
        }
        jedis.close();
    }

    private static String getFromDatabase(String key) {
        // 模拟从数据库查询
        return null;
    }
}

3. 加强参数校验

  • 原理:在应用层对输入参数进行严格校验,比如检查参数的类型、长度、格式等是否合法。如果参数不合法,直接返回错误信息,避免不合法的参数作为查询条件去查询Redis和数据库,从而防止缓存穿透。
  • 实现方式:以Spring Boot应用为例,可以使用注解进行参数校验。例如:
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Validated
public class ParameterValidationController {

    @GetMapping("/data")
    public String getData(@RequestParam @NotBlank String key) {
        // 合法参数才进行后续查询
        return "Process data for key: " + key;
    }
}

4. 动态调整缓存策略

  • 原理:分析业务数据的访问模式,对于一些频繁穿透的查询,可以调整缓存策略。比如将一些热点数据的缓存时间延长,或者对某些特定的查询模式进行特殊处理,减少穿透的可能性。
  • 实现方式:可以通过监控系统收集查询数据,分析哪些查询容易导致缓存穿透。例如,在应用中添加AOP切面,记录每次查询的键值和查询结果,定期分析数据。如果发现某个键频繁查询且穿透到数据库,可以通过配置中心动态调整该键的缓存时间或缓存策略。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class QueryMonitorAspect {

    @Around("execution(* com.example.demo.service.*.*(..))")
    public Object monitorQuery(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        try {
            Object[] args = joinPoint.getArgs();
            // 假设第一个参数是查询键
            String key = (String) args[0];
            long startTime = System.currentTimeMillis();
            result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            // 记录查询时间、键和结果,后续分析
            System.out.println("Query key: " + key + ", time: " + (endTime - startTime) + ", result: " + result);
        } catch (Throwable throwable) {
            throw throwable;
        }
        return result;
    }
}