MST

星途 面试题库

面试题:Redis缓存更新与实时性保障之高级难度

假设在高并发场景下,使用Redis作为缓存,如何设计一个合理的缓存更新机制来保障数据的实时性,同时尽可能减少缓存穿透和缓存雪崩的风险?请详细说明设计思路和关键代码实现要点。
48.1万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 数据实时性
    • 读写分离更新:在写操作时,同时更新数据库和 Redis 缓存。读操作先从缓存读,若缓存不存在再读数据库,并将数据回种到缓存。
    • 使用消息队列:写操作完成后,发送消息到消息队列,由消费者异步更新缓存,确保数据一致性且不影响主业务流程。
  2. 减少缓存穿透风险
    • 布隆过滤器:在查询缓存前,先通过布隆过滤器判断数据是否存在。若不存在,直接返回,避免查询数据库。布隆过滤器存在一定误判率,但可通过合理设置参数降低。
    • 缓存空值:当数据库查询结果为空时,在缓存中设置一个空值,并设置较短过期时间,防止后续相同查询穿透到数据库。
  3. 减少缓存雪崩风险
    • 随机过期时间:为缓存设置随机的过期时间,避免大量缓存同时过期。例如,原本过期时间为 60 秒,可设置为 55 - 65 秒之间的随机值。
    • 多级缓存:采用本地缓存(如 Guava Cache)和 Redis 缓存结合。先查本地缓存,不存在再查 Redis 缓存。当 Redis 缓存大量失效时,本地缓存可分担部分压力。
    • 服务降级:当系统压力过大时,对非核心业务进行服务降级,直接返回默认值或提示信息,避免因大量请求压垮数据库。

关键代码实现要点

  1. 读写分离更新
    • Java 示例(使用 Jedis 和 JDBC)
import redis.clients.jedis.Jedis;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class CacheAndDBUpdate {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String DB_URL = "jdbc:mysql://localhost:3306/yourdatabase";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "password";

    public static void main(String[] args) {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
        try {
            // 写操作
            String key = "exampleKey";
            String value = "exampleValue";
            // 更新数据库
            Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
            String updateSql = "UPDATE your_table SET column =? WHERE some_condition";
            PreparedStatement updateStmt = connection.prepareStatement(updateSql);
            updateStmt.setString(1, value);
            updateStmt.executeUpdate();
            // 更新缓存
            jedis.set(key, value);

            // 读操作
            String cachedValue = jedis.get(key);
            if (cachedValue == null) {
                String selectSql = "SELECT column FROM your_table WHERE some_condition";
                PreparedStatement selectStmt = connection.prepareStatement(selectSql);
                ResultSet resultSet = selectStmt.executeQuery();
                if (resultSet.next()) {
                    cachedValue = resultSet.getString(1);
                    jedis.set(key, cachedValue);
                }
            }
            System.out.println("Value from cache or db: " + cachedValue);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }
    }
}
  1. 布隆过滤器
    • Java 示例(使用 Google Guava 的 BloomFilter)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

public class BloomFilterExample {
    private static final int EXPECTED_ELEMENTS = 10000;
    private static final double FALSE_POSITIVE_RATE = 0.01;

    private static BloomFilter<String> bloomFilter = BloomFilter.create(
            Funnels.stringFunnel(), EXPECTED_ELEMENTS, FALSE_POSITIVE_RATE);

    public static void main(String[] args) {
        // 添加数据到布隆过滤器
        bloomFilter.put("exampleKey1");
        bloomFilter.put("exampleKey2");

        // 查询数据
        boolean mightContain1 = bloomFilter.mightContain("exampleKey1");
        boolean mightContain2 = bloomFilter.mightContain("exampleKey3");
        System.out.println("exampleKey1 might contain: " + mightContain1);
        System.out.println("exampleKey3 might contain: " + mightContain2);
    }
}
  1. 缓存空值
    • Java 示例(使用 Jedis)
import redis.clients.jedis.Jedis;

public class CacheNullValue {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final int NULL_CACHE_EXPIRE = 60; // 60 秒

    public static void main(String[] args) {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
        String key = "nonexistentKey";
        String value = jedis.get(key);
        if (value == null) {
            // 假设数据库查询结果也为空
            jedis.setex(key, NULL_CACHE_EXPIRE, "nullValue");
            value = "nullValue";
        }
        System.out.println("Value from cache: " + value);
        jedis.close();
    }
}
  1. 随机过期时间
    • Java 示例(使用 Jedis)
import redis.clients.jedis.Jedis;
import java.util.Random;

public class RandomExpiration {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final int BASE_EXPIRE = 60; // 基础过期时间 60 秒
    private static final int RANDOM_RANGE = 10; // 随机范围 10 秒

    public static void main(String[] args) {
        Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
        String key = "exampleKey";
        String value = "exampleValue";
        Random random = new Random();
        int randomExpire = BASE_EXPIRE + random.nextInt(RANDOM_RANGE * 2) - RANDOM_RANGE;
        jedis.setex(key, randomExpire, value);
        System.out.println("Set key with random expiration: " + randomExpire + " seconds");
        jedis.close();
    }
}