Redis 面试题#

1. Redis 数据类型#

问题: Redis 支持哪些数据类型?

答案:

类型说明使用场景
String字符串、整数、浮点数缓存、计数器
Hash键值对集合存储对象
List双向链表消息队列、时间线
Set无序唯一集合标签、共同好友
Sorted Set有序集合排行榜、延迟队列
Bitmap位图签到、在线状态
HyperLogLog基数统计UV 统计
Geo地理位置附近的人
Stream消息流消息队列

2. Redis 持久化#

问题: Redis 的持久化方式有哪些?

答案:

RDB(快照):

  • 定时将内存数据快照保存到磁盘
  • 文件紧凑,恢复速度快
  • 可能丢失最后一次快照后的数据
save 900 1      # 900秒内至少1个key变化则保存
save 300 10     # 300秒内至少10个key变化则保存
save 60 10000   # 60秒内至少10000个key变化则保存

AOF(追加文件):

  • 记录所有写操作命令
  • 数据安全性更高
  • 文件较大,恢复速度较慢
appendonly yes
appendfsync everysec  # 每秒同步

混合持久化(Redis 4.0+):

  • RDB + AOF 结合
  • 开头是 RDB 格式,后面是 AOF 格式

3. Redis 缓存问题#

问题: 什么是缓存穿透、缓存击穿、缓存雪崩?如何解决?

答案:

问题现象解决方案
缓存穿透查询不存在的数据,绕过缓存直达数据库布隆过滤器、缓存空值
缓存击穿热点key过期,大量请求直达数据库互斥锁、逻辑过期
缓存雪崩大量key同时过期,数据库压力激增随机过期时间、多级缓存

解决方案代码:

// 缓存空值防止穿透
public String getData(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        if (value == null) {
            // 缓存空值,短时间过期
            redis.setex(key, 60, "");
        } else {
            redis.setex(key, 3600, value);
        }
    }
    return value;
}

// 互斥锁防止击穿
public String getHotData(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 获取锁
        if (redis.setnx("lock:" + key, "1", 10)) {
            try {
                value = db.query(key);
                redis.setex(key, 3600, value);
            } finally {
                redis.del("lock:" + key);
            }
        } else {
            // 获取锁失败,短暂等待后重试
            Thread.sleep(100);
            return getHotData(key);
        }
    }
    return value;
}

4. Redis 高可用#

问题: Redis 如何实现高可用?

答案:

主从复制:

# 从节点配置
replicaof 192.168.1.100 6379
  • 数据冗余
  • 读写分离
  • 故障恢复需要手动切换

哨兵模式(Sentinel):

  • 监控主从节点
  • 自动故障转移
  • 最少需要 3 个哨兵节点
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000

集群模式(Cluster):

  • 数据分片(16384 个槽位)
  • 自动故障转移
  • 支持水平扩展
# 创建集群
redis-cli --cluster create \
  192.168.1.101:6379 192.168.1.102:6379 192.168.1.103:6379 \
  192.168.1.104:6379 192.168.1.105:6379 192.168.1.106:6379 \
  --cluster-replicas 1

5. Redis 性能优化#

问题: 如何优化 Redis 性能?

答案:

  1. 内存优化

    • 使用 Hash 存储小对象(ziplist 编码)
    • 设置合理的过期时间
    • 启用内存淘汰策略
  2. 命令优化

    • 使用 Pipeline 批量操作
    • 避免大 key(String > 10KB,集合 > 5000 个元素)
    • 使用 SCAN 替代 KEYS
  3. 架构优化

    • 读写分离
    • 使用连接池
    • 本地缓存 + Redis 多级缓存
# 查看大 key
redis-cli --bigkeys

# 内存分析
redis-cli --memkeys

# 慢查询
slowlog get 10

6. Redis 架构对比与热点 Key#

问题: 请对比 Redis Sentinel(哨兵)和 Redis Cluster(集群)的适用场景。Cluster 模式下如何处理"热点 Key"问题?

答案:

Sentinel vs Cluster:

特性SentinelCluster
架构主从复制 + 哨兵监控分片集群
数据分片不支持支持(16384 个槽)
容量受单机内存限制可水平扩展
写入性能单机写入多节点写入
故障转移自动自动
适用场景数据量小、高可用大数据量、高并发

适用场景:

  • Sentinel:缓存数据 < 100GB,读多写少,需要高可用
  • Cluster:缓存数据 > 100GB,写入量大,需要水平扩展

热点 Key 问题:

现象:某个 Key 被频繁访问,导致单个节点负载过高。

解决方案:

  1. 本地缓存 + Redis

    // 本地缓存(Caffeine/Guava)+ Redis
    @Cacheable(value = "hotKey", key = "#id")
    public String getData(String id) {
        return redisTemplate.opsForValue().get(id);
    }
  2. Key 拆分

    原 Key: user:1001
    拆分为: user:1001:0, user:1001:1, user:1001:2
    读取时随机选择一个
  3. 读写分离

    • 从节点分担读压力
    • 但主从同步有延迟
  4. 使用 Proxy 层

    Codis/Twemproxy 可以在代理层做热点 Key 监控和缓存

7. Redis 持久化灾难恢复#

问题: 如果 Redis 同时开启 AOF 和 RDB,重启时会加载哪个文件?为什么?

答案:

加载优先级:

  1. AOF 优先级高于 RDB

    • 如果 AOF 开启且文件存在,优先加载 AOF
    • 因为 AOF 数据更完整(记录每条写命令)
  2. 加载逻辑:

    启动时:
    1. 检查 AOF 是否开启
    2. 如果开启且 AOF 文件存在 → 加载 AOF
    3. 否则 → 加载 RDB

原因:

  • AOF 是追加写入,数据更实时
  • RDB 是快照,可能丢失最后一次快照后的数据
  • AOF 可以通过 bgrewriteaof 压缩

灾难恢复场景:

# 场景1:AOF 文件损坏
# 使用 redis-check-aof 修复
redis-check-aof --fix appendonly.aof

# 场景2:AOF 和 RDB 都损坏
# 从备份恢复,或清空数据重启

# 场景3:需要恢复到某个时间点
# 使用 RDB 历史备份 + AOF 增量

最佳实践:

# 同时开启 RDB 和 AOF
save 900 1
appendonly yes
appendfsync everysec

# 定期备份
cp /var/lib/redis/dump.rdb /backup/redis/dump-$(date +%Y%m%d).rdb
cp /var/lib/redis/appendonly.aof /backup/redis/aof-$(date +%Y%m%d).aof