Java面试Redis案例

wen java案例 3

本文目录导读:

Java面试Redis案例

  1. 高频案例:缓存穿透 / 击穿 / 雪崩
  2. 分布式锁(重点考察)
  3. 缓存与数据库双写一致性
  4. 热点数据缓存(商品详情)
  5. 排行榜(ZSet 应用)
  6. 面试回答话术模板
  7. 备战建议

下面给你一套 Java 面试中关于 Redis 的高频案例讲解,从简单到深入,覆盖缓存穿透、击穿、雪崩、分布式锁、数据一致性等核心场景,并附上代码示例回答话术


高频案例:缓存穿透 / 击穿 / 雪崩

这是面试官必问的“三兄弟”,核心考察你对缓存异常场景的理解和解决方案。

缓存穿透(查不存在的数据)

现象:大量请求查询一个缓存和数据库都不存在的 key,导致请求直接打到数据库。

解决方案

  • 缓存空值:如果数据库查不到,也把空值缓存起来,设置较短的过期时间(如 5 分钟)。
  • 布隆过滤器:在缓存之前加一层 Bloom Filter,拦截不存在的 key。

代码示例(缓存空值)

public String getData(String key) {
    // 1. 查缓存
    String value = redis.get(key);
    if (value != null) {
        return value;
    }
    // 2. 缓存未命中,查数据库
    String dbValue = queryDB(key);
    if (dbValue == null) {
        // 3. 缓存空值,防止穿透
        redis.set(key, "", 300);  // 5分钟过期
        return null;
    }
    // 4. 写回缓存
    redis.set(key, dbValue, 3600);
    return dbValue;
}

缓存击穿(热点 key 过期)

现象:一个热点 key 在过期瞬间,大量并发请求同时打向数据库。

解决方案

  • 互斥锁:只允许一个线程去重建缓存,其他线程等待。
  • 逻辑过期:不设置物理过期时间,而是存一个逻辑过期时间,异步更新。

代码示例(互斥锁)

public String getDataWithLock(String key) {
    String value = redis.get(key);
    if (value != null) {
        return value;
    }
    // 加锁(使用 Redis SETNX 实现)
    String lockKey = "lock:" + key;
    boolean locked = redis.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
    if (locked) {
        try {
            // 二次检查(double check)
            value = redis.get(key);
            if (value != null) return value;
            // 查数据库
            value = queryDB(key);
            redis.set(key, value, 3600);
            return value;
        } finally {
            redis.delete(lockKey);
        }
    } else {
        // 等待重试
        Thread.sleep(100);
        return getDataWithLock(key);  // 递归重试
    }
}

缓存雪崩(大量 key 同时过期)

现象:大量 key 在同一时间过期,导致请求全部打到数据库。

解决方案

  • 随机过期时间:在设置过期时间时加上随机值。
  • 多级缓存:本地缓存(Caffeine)+ Redis。
  • 熔断降级:数据库压力过大时,直接返回默认值。
// 设置过期时间时加随机值
int expireTime = 3600 + new Random().nextInt(600);  // 1小时 + 0~10分钟随机
redis.set(key, value, expireTime);

分布式锁(重点考察)

场景

在分布式系统中,多个服务同时操作同一个资源(如库存扣减),需要保证互斥性。

要求

  1. 互斥性:同一时刻只有一个线程能获取锁。
  2. 防死锁:锁必须设置过期时间。
  3. 防误删:只能删除自己的锁(加 value 标识)。

代码示例(Redisson 实现)

@Autowired
private RedissonClient redissonClient;
public void deductStock() {
    RLock lock = redissonClient.getLock("stock:1001");
    try {
        // 尝试加锁,等待 10 秒,锁有效 30 秒
        if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
            // 业务逻辑:扣减库存
            int stock = getStock();
            if (stock > 0) {
                reduceStock();
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();  // 释放锁
    }
}

面试关键点

  • 为什么不用 SETNX 自己实现?—— Redisson 有watch dog 自动续期可重入等特性。
  • 锁过期时间设置多少合适?—— 要结合业务执行时间,或使用 Redisson 自动续期。

缓存与数据库双写一致性

场景

更新数据库后,如何保证缓存一致?

方案对比

方案 说明 问题
先更新数据库,再删缓存 推荐 可能短暂不一致,但最终一致
先删缓存,再更新数据库 不推荐 并发下容易脏数据
延迟双删 先删缓存 → 更新数据库 → 延迟再删 解决并发问题

推荐方案(先更新 DB,再删缓存)

@Transactional
public void updateData(String id, String newValue) {
    // 1. 更新数据库
    database.update(id, newValue);
    // 2. 删除缓存
    redis.delete("key:" + id);
}

补充(延迟双删)

public void updateData(String id, String newValue) {
    redis.delete("key:" + id);  // 先删缓存
    database.update(id, newValue);
    // 延迟 500ms 再删一次
    Thread.sleep(500);
    redis.delete("key:" + id);
}

热点数据缓存(商品详情)

场景

商品详情页高并发访问量巨大,如何设计?

方案

  1. 多级缓存:本地缓存(Caffeine)→ Redis → 数据库。
  2. 缓存预热:项目启动时,把热门商品加载到缓存。
  3. 缓存更新策略:定时刷新 + 主动失效。
// 缓存预热
@Component
public class CachePreload implements ApplicationRunner {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public void run(ApplicationArguments args) {
        List<Product> hotProducts = productService.getHotProducts();
        for (Product product : hotProducts) {
            redisTemplate.opsForValue().set("product:" + product.getId(), product, 1, TimeUnit.HOURS);
        }
    }
}

排行榜(ZSet 应用)

场景

游戏排行榜、销售榜单等。

代码示例

// 更新分数
redisTemplate.opsForZSet().add("rank", "user:1001", 85.5);
// 获取 Top 10
Set<Object> top10 = redisTemplate.opsForZSet().reverseRange("rank", 0, 9);
// 获取某用户排名
Long rank = redisTemplate.opsForZSet().reverseRank("rank", "user:1001");

面试回答话术模板

面试官问:“你们项目中 Redis 是怎么用的?”

建议从以下三方面回答:

缓存场景:商品详情缓存、用户信息缓存,使用 Spring Cache + Redis。
2. 分布式锁:库存扣减时使用 Redisson 分布式锁防止超卖。
3. 排行榜/计数器:点赞用 incr,排行榜用 ZSet。

然后引出经典问题:

“在缓存使用过程中,我们也考虑到了缓存穿透、击穿、雪崩。
穿透我们用缓存空值+布隆过滤器;
击穿我们用互斥锁;
雪崩用过期时间加随机值。
对于一致性,我们采用先更新数据库再删缓存的策略,最终一致即可。”

备战建议

知识点 必备程度 面试频率
缓存穿透/击穿/雪崩 90%
分布式锁 80%
缓存一致性 70%
ZSet 排行榜 40%
持久化机制(RDB/AOF) 50%
过期删除策略 30%

抱歉,评论功能暂时关闭!