Skip to content

Redis 面试题

Redis 基础

Q1: Redis 有哪些数据类型?

┌─────────────────────────────────────────────────────────────────┐
│                      Redis 数据类型                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   String      - 字符串       SDS (Simple Dynamic String)        │
│   Hash        - 哈希表        Dict (Hash Table)                 │
│   List        - 列表         QuickList (压缩列表+双向链表)       │
│   Set         - 集合         IntSet + Hash Table               │
│   Sorted Set  - 有序集合      ZipList + SkipList               │
│                                                                  │
│   高级类型:                                                    │
│   Bitmap      - 位图          String 类型                                                │
│   HyperLogLog - 基数统计      String 类型                                                │
│   Geo         - 地理坐标      Sorted Set                                               │
│   Stream      - 流数据        Radix Tree                                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
类型应用场景底层实现
String缓存、计数器、分布式锁SDS
Hash对象存储Hash Table
List消息队列、排行榜QuickList
Set标签、好友关系IntSet + HT
ZSet有序集合、排行榜SkipList
Bitmap用户签到、活跃统计String
HyperLogLogUV 统计String
Geo附近的人Sorted Set
Stream消息队列RadixTree

Q2: Redis 的持久化机制?

RDB(Redis Database)

┌─────────────────────────────────────────────────────────────────┐
│                         RDB 持久化                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   原理:定时生成数据快照                                         │
│                                                                  │
│   触发方式:                                                    │
│   1. 定时任务(redis.conf 配置)                               │
│   2. BGSAVE 命令                                               │
│   3. SHUTDOWN 时                                               │
│                                                                  │
│   优点:恢复速度快,适合备份                                    │
│   缺点:可能丢失最后一次快照后的数据                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

AOF(Append Only File)

┌─────────────────────────────────────────────────────────────────┐
│                         AOF 持久化                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   原理:记录每个写命令                                           │
│                                                                  │
│   同步策略:                                                    │
│   - always     每条命令同步(最安全,性能最差)                │
│   - everysec   每秒同步(推荐)                                │
│   - no         由系统同步(性能最好,数据最不安全)            │
│                                                                  │
│   重写机制:                                                    │
│   BGREWRITEAOF - 压缩 AOF 文件                                 │
│                                                                  │
│   优点:数据安全性高                                            │
│   缺点:文件体积大,恢复速度慢                                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

RDB 和 AOF 对比

对比RDBAOF
文件大小
恢复速度
数据安全性可能丢失数据可配置
性能影响后台 fork 进程每秒刷盘
场景备份、恢复持久化要求高

Q3: Redis 的过期策略?

三种过期策略

bash
# 1. 定时删除(定时扫描)
# 设置过期时间时创建定时器,到期删除
# 优点:内存友好
# 缺点:CPU 压力大

# 2. 惰性删除(访问时检查)
# 访问 key 时检查是否过期
# 优点:CPU 友好
# 缺点:内存可能泄漏

# 3. 定期删除(折中方案)
# 周期性扫描,定期检查
# Redis 默认使用惰性删除 + 定期删除

内存淘汰策略

bash
# 当内存达到 maxmemory 时
maxmemory-policy volatile-lru

# 策略选项:
# - noeviction    不淘汰,返回错误
# - allkeys-lru   所有 key LRU 淘汰
# - volatile-lru  有过期时间的 key LRU 淘汰
# - allkeys-random 所有 key 随机淘汰
# - volatile-random 有过期时间的 key 随机淘汰
# - volatile-ttl   有过期时间的 key 优先淘汰 TTL 短的

Q4: Redis 的线程模型?

┌─────────────────────────────────────────────────────────────────┐
│                      Redis 单线程模型                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────┐                                              │
│   │ Client      │                                              │
│   └──────┬──────┘                                              │
│          │                                                     │
│          ▼                                                     │
│   ┌─────────────┐                                              │
│   │   I/O 多路复用│                                              │
│   │  (epoll/select)│                                           │
│   └──────┬──────┘                                              │
│          │                                                     │
│          ▼                                                     │
│   ┌─────────────┐                                              │
│   │  命令队列    │                                              │
│   └──────┬──────┘                                              │
│          │                                                     │
│          ▼                                                     │
│   ┌─────────────┐                                              │
│   │  单线程执行  │  ← Redis 6.0 之前是单线程                   │
│   │  (SocketIO + 命令执行) │                                    │
│   └─────────────┘                                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

注意

  • Redis 6.0 之前是单线程(I/O 和命令执行都是单线程)
  • Redis 6.0 之后 I/O 变成多线程,但命令执行还是单线程
  • 持久化、集群通信等由后台线程处理

Redis 集群

Q5: Redis 主从复制原理?

┌─────────────────────────────────────────────────────────────────┐
│                      主从复制架构                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                      ┌──────────┐                               │
│                      │  Master   │                               │
│                      │  (主节点) │                               │
│                      └────┬─────┘                               │
│                           │                                     │
│                           │  SYNC / PSYNC                      │
│            ┌──────────────┼──────────────┐                     │
│            │              │              │                     │
│            ▼              ▼              ▼                     │
│       ┌────────┐    ┌────────┐    ┌────────┐                  │
│       │Slave 1 │    │Slave 2 │    │Slave 3 │                  │
│       │ (从节点) │    │ (从节点) │    │ (从节点) │               │
│       └────────┘    └────────┘    └────────┘                  │
│                                                                  │
│   主节点:写 + 读                                                │
│   从节点:读(分担压力)                                         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

复制过程

  1. 从节点发送 SYNC 命令
  2. 主节点执行 BGSAVE,生成 RDB 文件
  3. 主节点发送 RDB 文件给从节点
  4. 从节点加载 RDB 文件
  5. 主节点发送缓冲区中的写命令
  6. 后续主节点的写命令同步发送给从节点

Redis 4.0+ 支持 PSYNC

  • 完整同步(PSYNC ?-1):首次复制
  • 部分同步(PSYNC <runid> <offset>):断线重连

Q6: Redis Sentinel(哨兵)原理?

┌─────────────────────────────────────────────────────────────────┐
│                      Redis Sentinel 架构                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                      ┌──────────┐                               │
│                      │ Sentinel  │                               │
│                      │  (哨兵)   │                               │
│                      └────┬─────┘                               │
│                           │                                     │
│            ┌──────────────┼──────────────┐                     │
│            │              │              │                     │
│            ▼              ▼              ▼                     │
│       ┌────────┐    ┌────────┐    ┌────────┐                  │
│       │Master  │    │ Slave1 │    │ Slave2 │                  │
│       └────┬───┘    └────────┘    └────────┘                  │
│            │                                                 │
│            │ 故障检测                                          │
│            ▼                                                  │
│       自动故障转移                                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

哨兵职责

  1. 监控:定期检查主从节点是否存活
  2. 通知:故障时通知应用方
  3. 自动故障转移:主节点宕机时自动选举新主节点
  4. 配置中心:提供当前主节点地址

Q7: Redis Cluster 原理?

┌─────────────────────────────────────────────────────────────────┐
│                      Redis Cluster 架构                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   16384 个槽位分片                                              │
│                                                                  │
│   ┌────────┐  ┌────────┐  ┌────────┐                          │
│   │ Node 1 │  │ Node 2 │  │ Node 3 │                          │
│   │ 0-5460 │  │5461-10922│10923-16383│                      │
│   └────┬───┘  └────┬───┘  └────┬───┘                          │
│        │           │           │                               │
│        ▼           ▼           ▼                               │
│   ┌────────┐  ┌────────┐  ┌────────┐                          │
│   │Master 1│  │Master 2│  │Master 3│                          │
│   └────────┘  └────────┘  └────────┘                          │
│        │           │           │                               │
│        ▼           ▼           ▼                               │
│   ┌────────┐  ┌────────┐  ┌────────┐                          │
│   │ Slave 1 │  │ Slave 2 │  │ Slave 3 │                          │
│   └────────┘  └────────┘  └────────┘                          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

特点

  • 数据分片(16384 个槽)
  • 去中心化(无单一主节点)
  • 高可用(每个主节点至少一个从节点)
  • 自动故障转移

Redis 分布式锁

Q8: Redis 分布式锁如何实现?

基本实现

java
// 加锁
public boolean lock(String key, String value, long expireTime) {
    String result = redisTemplate.opsForValue()
        .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(result);
}

// 解锁(Lua 脚本保证原子性)
public boolean unlock(String key, String value) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                    "then return redis.call('del', KEYS[1]) " +
                    "else return 0 end";

    return redisTemplate.execute(
        new DefaultRedisScript<>(script, Long.class),
        Collections.singletonList(key),
        value
    ) == 1L;
}

为什么要用 Lua 脚本?

  • 解锁时需要先判断锁是否是自己加的,再删除
  • 如果分两步执行,在判断和删除之间锁过期了,会误删别人的锁
  • Lua 脚本保证这两步原子执行

Q9: Redisson 分布式锁?

java
// Redisson 分布式锁
RLock lock = redissonClient.getLock("order:lock:" + orderId);
try {
    // 等待锁30秒,锁定后自动60秒释放
    if (lock.tryLock(30, 60, TimeUnit.SECONDS)) {
        // 业务逻辑
    }
} finally {
    lock.unlock();
}

Redisson 特点

  • 自动续期(看门狗机制)
  • 可重入锁
  • 公平锁
  • 读写锁

缓存问题

Q10: 什么是缓存穿透?如何解决?

缓存穿透:查询不存在的数据,每次都穿透到数据库

┌─────────────────────────────────────────────────────────────────┐
│                      缓存穿透示意                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   请求 ──→ Redis ──→ DB ──→ 返回 NULL                         │
│              ↑         ↑                                        │
│              │         │                                        │
│         缓存不存在  数据库也不存在                                │
│              │         │                                        │
│              ←─────────┘                                        │
│            每次请求都打满数据库                                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

解决方案

1. 布隆过滤器

java
BloomFilter<String> bloomFilter = BloomFilter.create(
    Funnels.stringFunnel(Charset.defaultCharset()),
    1000000,  // 预期元素数量
    0.01      // 误判率
);

// 查询前先检查布隆过滤器
if (!bloomFilter.mightContain(key)) {
    return null;  // 一定不存在
}

2. 缓存空值

java
// 缓存空值,设置较短过期时间
if (user == null) {
    redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
} else {
    redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}

Q11: 什么是缓存击穿?如何解决?

缓存击穿:热点 key 过期,瞬间大量请求打满数据库

┌─────────────────────────────────────────────────────────────────┐
│                      缓存击穿示意                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   热点 key 过期                                                 │
│        │                                                        │
│        ▼                                                        │
│   ┌─────────────────────────────────────────────────┐          │
│   │        瞬间大量请求同时发现缓存不存在            │          │
│   └─────────────────────────────────────────────────┘          │
│        │                                                        │
│        ▼                                                        │
│       DB                                                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

解决方案

1. 互斥锁

java
public User getUserMutex(Long id) {
    String key = "user:" + id;
    User user = redisTemplate.opsForValue().get(key);

    if (user == null) {
        String lockKey = "lock:" + key;
        String lockValue = UUID.randomUUID().toString();

        if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS)) {
            try {
                user = userDao.findById(id);
                redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
            } finally {
                redisTemplate.delete(lockKey);
            }
        } else {
            Thread.sleep(50);
            return getUserMutex(id);
        }
    }

    return user;
}

2. 热点数据永不过期 + 异步更新


Q12: 什么是缓存雪崩?如何解决?

缓存雪崩:大量缓存同时过期,导致数据库压力过大

┌─────────────────────────────────────────────────────────────────┐
│                      缓存雪崩示意                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   大量 key 同时过期                                              │
│        │                                                        │
│        ▼                                                        │
│   ┌─────────────────────────────────────────────────┐          │
│   │        大量请求同时发现缓存过期                   │          │
│   └─────────────────────────────────────────────────┘          │
│        │                                                        │
│        ▼                                                        │
│       DB                                                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

解决方案

1. 过期时间加随机值

java
redisTemplate.opsForValue().set(key, value, 1 + random.nextInt(5), TimeUnit.HOURS);

2. 热点数据永不过期

3. Redis Cluster 高可用

4. 服务降级/限流


Redis 实战

Q13: Redis 和 Memcached 的区别?

对比RedisMemcached
数据类型丰富(5种+)只有 String
持久化支持 RDB/AOF不支持
集群原生支持 Cluster需二次开发
线程模型单线程 + 多线程(I/O)多线程
内存管理自行实现slab allocation
适用场景复杂数据结构、持久化简单缓存

Q14: Redis 的并发竞争问题?

问题:多个客户端同时修改同一个 key,导致数据不一致

解决方案

1. 分布式锁(前面已讲)

2. WATCH + MULTI + EXEC(乐观锁)

java
// 监视 key
redisTemplate.watch(key);

// 开启事务
redisTemplate.multi();

// 修改操作
redisTemplate.opsForValue().increment(key);

// 执行事务
redisTemplate.exec();

3. 队列串行化 将并发请求放入队列,串行处理


Q15: Redis 性能优化?

1. 避免大 key

bash
# 查看大 key
redis-cli --bigkeys

# 删除大 key 用 UNLINK(异步删除)
redis-cli UNLINK big_key

2. 使用 Pipeline

java
// 批量命令,减少网络开销
List<Object> results = redisTemplate.executePipelined(new SessionCallback<Object>() {
    @Override
    public Object execute(RedisOperations operations) throws DataAccessException {
        for (int i = 0; i < 10000; i++) {
            operations.opsForValue().increment("key:" + i);
        }
        return null;
    }
});

3. 合理选择数据结构

java
// 存储用户信息,用 Hash 而不是 String
redisTemplate.opsForHash().put("user:100", "name", "张三");

// 排行榜,用 ZSet
redisTemplate.opsForZSet().add("ranking", "user:100", 1000);

总结

Redis 高频面试知识点

知识点面试频率
5 种数据结构⭐⭐⭐⭐⭐
持久化 RDB/AOF⭐⭐⭐⭐
主从复制原理⭐⭐⭐⭐
Sentinel / Cluster⭐⭐⭐⭐
分布式锁⭐⭐⭐⭐⭐
缓存穿透/击穿/雪崩⭐⭐⭐⭐⭐
Redis 线程模型⭐⭐⭐⭐
内存淘汰策略⭐⭐⭐