面试被问 Redis?这 3 个问题 90% 的人都答不对

🔥 问题一:Redis 单线程为什么还能支撑 10w+ QPS?

面试官潜台词: 考察你对 Redis 高性能原理的理解深度。

90% 的人答错:

"因为 Redis 是纯内存操作,所以快。"

❌ 太浅了! 内存操作只是基础,单线程设计才是精髓。

正确答案:

scss 复制代码
// Redis 的核心设计:单线程 + IO多路复用
// 类比 Java NIO 的 Selector 机制

// 传统阻塞IO(低效)
while (true) {
    Socket socket = serverSocket.accept(); // 阻塞!
    handle(socket); // 处理完才能接受下一个
}

// Redis 的 IO多路复用(高效)
while (!stop) {
    // 1. 批量获取就绪的 socket(非阻塞)
    readySockets = epoll_wait(epfd, events, maxevents, timeout);
    
    // 2. 逐个处理(纯内存操作,极快)
    for (i = 0; i < readySockets; i++) {
        handle(events[i].data.fd);
    }
}

核心原因 3 点:

设计 作用
纯内存操作 纳秒级响应,比磁盘快 10 万倍
单线程模型 避免上下文切换、锁竞争,代码简单高效
IO多路复用 一个线程管理数万连接,epoll 性能炸裂

💡 一句话总结: Redis 把"单线程"做成了优势------没有锁、没有切换、没有竞争,把内存性能压榨到极致。


🔥 问题二:缓存穿透、击穿、雪崩,到底啥区别?

面试官潜台词: 考察你对缓存架构风险的理解和解决方案。

90% 的人混淆:

"都是缓存失效了,然后请求打到数据库..."

❌ 概念不清! 三者场景和解决方案完全不同。

一张图讲清楚:

rust 复制代码
缓存穿透          缓存击穿            缓存雪崩
   ↓                ↓                  ↓
查询不存在的数据   热点key过期         大量key同时过期
   ↓                ↓                  ↓
缓存无 -> DB无    缓存无 -> DB有      缓存集体失效
   ↓                ↓                  ↓
每次都查DB        瞬间高并发查DB       DB压力暴增

代码级解决方案:

1️⃣ 缓存穿透 --- 布隆过滤器

typescript 复制代码
@Service
public class CacheService {
    
    @Autowired
    private RedissonClient redisson;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    // 布隆过滤器:快速判断"一定不存在"或"可能存在"
    public String getData(String key) {
        RBloomFilter<String> bloomFilter = 
            redisson.getBloomFilter("user:bloom");
        
        // 1. 布隆过滤器拦截
        if (!bloomFilter.contains(key)) {
            return null; // 一定不存在,直接返回
        }
        
        // 2. 查缓存
        String value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            return value;
        }
        
        // 3. 查数据库(布隆过滤器说有,但实际可能没有)
        return queryDB(key);
    }
}

2️⃣ 缓存击穿 --- 互斥锁 + 逻辑过期

ini 复制代码
public String getHotData(String key) {
    String json = redisTemplate.opsForValue().get(key);
    
    // 1. 判断是否逻辑过期
    RedisData redisData = JSON.parseObject(json, RedisData.class);
    if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
        return redisData.getData(); // 未过期,直接返回
    }
    
    // 2. 已过期,尝试获取锁重建缓存
    String lockKey = "lock:" + key;
    boolean isLock = tryLock(lockKey, 10);
    
    if (isLock) {
        // 双重检查,防止重复重建
        json = redisTemplate.opsForValue().get(key);
        redisData = JSON.parseObject(json, RedisData.class);
        
        if (redisData.getExpireTime().isBefore(LocalDateTime.now())) {
            // 开启独立线程重建缓存
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    String newData = queryDB(key);
                    saveRedisWithLogicalExpire(key, newData, 30L);
                } finally {
                    unlock(lockKey);
                }
            });
        }
    }
    
    // 3. 返回过期数据(保证可用性)
    return redisData.getData();
}

3️⃣ 缓存雪崩 --- 多级缓存 + 随机过期

scss 复制代码
@Configuration
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        // Caffeine 本地缓存 + Redis 分布式缓存
        CaffeineCacheManager localCache = new CaffeineCacheManager();
        localCache.setCaffeine(
            Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
        );
        
        return new CompositeCacheManager(localCache, redisCacheManager());
    }
}

// 设置随机过期时间,避免同时失效
public void setWithRandomExpire(String key, String value, long baseExpire) {
    // 基础过期时间 + 随机 0-300 秒
    long expire = baseExpire + RandomUtil.randomLong(0, 300);
    redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}

💡 记忆口诀:

  • 穿透查不到(布隆挡)

  • 击穿过期了(锁来保)

  • 雪崩集体挂(多级+随机)


🔥 问题三:分布式锁用 setnx 就够了吗?

面试官潜台词: 考察你对分布式锁完整实现的理解。

90% 的人踩坑:

typescript 复制代码
// 错误示范!生产环境别这么写
public void wrongLock(String key) {
    Boolean success = redisTemplate.opsForValue()
        .setIfAbsent(key, "1"); // setnx
    
    if (success) {
        // 执行业务逻辑...
        // 如果这里抛异常或宕机,锁永远释放不了!
        redisTemplate.delete(key);
    }
}

❌ 3 个致命问题:

  1. 没设置过期时间 → 死锁
  2. 业务没执行完就过期 → 误删别人的锁
  3. 不是原子操作 → 竞态条件

正确姿势:Redisson 实现

csharp 复制代码
@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redisson;
    
    public void createOrder(Long userId) {
        // 1. 获取锁(可重入、自动续期)
        RLock lock = redisson.getLock("order:user:" + userId);
        
        try {
            // 等待10秒,锁30秒自动过期,看门狗自动续期
            boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);
            
            if (!isLock) {
                throw new RuntimeException("获取锁失败,请稍后重试");
            }
            
            // 2. 执行业务(锁内操作要尽可能快!)
            doCreateOrder(userId);
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁被中断");
        } finally {
            // 3. 释放锁(判断是不是自己加的锁)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Redisson 看门狗原理:

markdown 复制代码
业务线程
   │
   ├── 获取锁成功 ──┐
   │                │
   │                ▼
   │         启动看门狗(Watch Dog)
   │         每隔 10 秒检查:锁还在吗?
   │                │
   │                ├── 还在 → 续期到 30 秒
   │                └── 不在了 → 停止看门狗
   │
   ├── 业务执行中... ──┐
   │                   │
   └── 业务完成 ───────┘
                       │
                       ▼
                  释放锁 + 停止看门狗

💡 核心要点:

  • 原子性:加锁必须带过期时间(Lua脚本保证)

  • 可重入:同一线程可以多次获取锁

  • 自动续期:看门狗防止业务未完成锁过期

  • 谁加谁删:释放锁前判断持有者


📝 面试总结

问题 核心考点 一句话答案
单线程快? IO模型、内存操作 单线程+IO多路复用,无锁无切换
三大问题? 缓存架构风险 穿透布隆挡、击穿孔锁保、雪崩多级+随机
分布式锁? 锁的完整性 setnx+过期+看门狗+Lua原子性
相关推荐
金牌归来发现妻女流落街头2 小时前
【Spring AMQP 三大交换机】
后端·spring
重庆小透明2 小时前
微服务,不仅仅是“小服务”
java·后端·spring cloud·微服务·云原生·架构
孟沐2 小时前
JDBC 入门大白话文档
后端
李长渊哦2 小时前
OpenClaw 本地部署完全指南:从环境验证到启动运行
后端·arcgis
Java编程爱好者2 小时前
Spring Boot 中关于 Bean 加载、实例化、初始化全生命周期的扩展点
后端
七牛云行业应用2 小时前
别瞎折腾了!4 步排查法,手把手教你搞定 OpenClaw Skills 各种安装报错
后端·openai·agent
Java编程爱好者2 小时前
DBA 经验:MySQL性能最重要的参数只有2个!
后端
武子康2 小时前
大数据-245 离线数仓 - 电商分析 Hive 拉链表入门实战:缓慢变化维 SCD 类型、建表加载与常见错误速查
大数据·后端·apache hive
huahailing10242 小时前
Spring Boot 异步事务最佳实践:TransactionTemplate 实战指南
数据库·spring boot·后端