Skip to main content
Redis 面试高频考点,覆盖数据结构、持久化、集群、缓存一致性、性能优化等核心知识。

一、数据结构

Q1: Redis 的五种基本数据类型及底层实现

类型底层实现应用场景
StringSDS(动态字符串)缓存、计数器、分布式锁
Hashziplist / hashtable对象属性存储
Listquicklist(ziplist + 链表)消息队列、时间线
Setintset / hashtable标签、共同好友
ZSetziplist / skiplist + hashtable排行榜、优先队列

Q2: 为什么用 SDS 而不是 C 字符串?

特性C 字符串SDS
获取长度O(N) 遍历O(1) 直接读 len
缓冲区溢出可能溢出自动扩容
二进制安全❌ 不支持 \0✅ 支持任意二进制
内存分配每次修改都分配空间预分配、惰性释放
struct sdshdr {
    int len;      // 已用长度
    int free;     // 剩余空间
    char buf[];   // 实际数据
};

Q3: ZSet 为什么用跳表而不是红黑树?

特性跳表红黑树
实现复杂度简单复杂
范围查询✅ O(logN + M)需要中序遍历
内存占用略多略少
并发友好更容易加锁旋转操作复杂
结论: 跳表实现简单,范围查询效率高,更适合 Redis 场景。

Q4: 常用的扩展数据类型

类型说明应用场景
Bitmap位图用户签到、在线状态
HyperLogLog基数统计(误差 0.81%)UV 统计
Geo地理位置附近的人、门店
Stream消息流消息队列(Redis 5.0+)
# Bitmap 签到
SETBIT user:sign:1001:202401 1 1  # 1月2日签到
BITCOUNT user:sign:1001:202401    # 统计签到天数

# HyperLogLog UV
PFADD uv:20240101 user1 user2 user3
PFCOUNT uv:20240101

二、持久化

Q5: RDB 和 AOF 的区别

特性RDBAOF
原理内存快照命令日志
文件大小小(二进制压缩)大(文本命令)
恢复速度
数据安全可能丢失分钟级数据最多丢 1 秒
性能影响fork 子进程时影响everysec 影响小
推荐: 同时开启 RDB + AOF,优先用 AOF 恢复。

Q6: AOF 的同步策略

appendfsync always    # 每条命令都刷盘,最安全,性能最差
appendfsync everysec  # 每秒刷盘(默认),折中方案
appendfsync no        # 不主动刷盘,由 OS 决定,性能最好,风险最大
生产建议: 使用 everysec,最多丢失 1 秒数据。

Q7: AOF 重写机制

为什么需要重写?
  • AOF 文件持续增长
  • 很多命令可以合并(如多次 INCR 合并为 SET)
重写流程:
  1. fork 子进程,读取当前内存数据
  2. 子进程写入新 AOF 文件
  3. 主进程继续处理命令,增量写入 AOF 重写缓冲区
  4. 子进程完成后,将缓冲区内容追加到新文件
  5. 原子替换旧 AOF 文件

Q8: 混合持久化

原理(Redis 4.0+):
  • AOF 重写时,先写入 RDB 格式快照
  • 快照后的增量命令以 AOF 格式追加
优点:
  • 恢复速度快(RDB 部分)
  • 数据安全性高(AOF 部分)
aof-use-rdb-preamble yes  # 开启混合持久化

三、集群与高可用

Q9: 主从复制原理

全量复制:
  1. Slave 发送 PSYNC 命令
  2. Master 执行 BGSAVE,生成 RDB
  3. Master 发送 RDB 给 Slave
  4. Master 发送复制缓冲区内容
增量复制(Redis 2.8+):
  • 使用 repl_backlog 环形缓冲区
  • Slave 断线重连后,发送 offset
  • 如果 offset 在 backlog 内,只同步增量

Q10: 哨兵模式的选主流程

1. 主观下线(SDOWN):
  • Sentinel 认为 Master 不可用
2. 客观下线(ODOWN):
  • 超过 quorum 个 Sentinel 都认为 Master 不可用
3. 选举 Sentinel Leader(Raft) 4. Sentinel Leader 选新 Master:
  1. 过滤不健康的 Slave
  2. 按优先级 slave-priority 排序
  3. 优先级相同,选复制偏移量最大的
  4. 偏移量相同,选 runid 最小的

Q11: Cluster 模式的特点

槽位分配:
  • 固定 16384 个槽位
  • 数据按 CRC16(key) % 16384 分配到槽
多 key 操作限制:
  • 多 key 命令(MGET、MSET)必须在同一槽
  • 使用 hash tag:{user}:1001{user}:1002 在同一槽
# hash tag 用法
SET {order}:1001 value1
SET {order}:1002 value2
MGET {order}:1001 {order}:1002  # ✅ 可以

四、缓存问题

Q12: 缓存穿透、击穿、雪崩

问题原因解决方案
穿透查询不存在的数据布隆过滤器、缓存空值
击穿热点 key 过期互斥锁、永不过期 + 异步更新
雪崩大量 key 同时过期随机过期时间、多级缓存
缓存穿透 - 布隆过滤器:
# 查询前先判断
if not bloom_filter.exists(key):
    return None  # 一定不存在
data = cache.get(key) or db.get(key)
缓存击穿 - 互斥锁:
def get_data(key):
    data = cache.get(key)
    if data:
        return data
    
    if acquire_lock(key):
        try:
            data = cache.get(key)  # 双重检查
            if not data:
                data = db.get(key)
                cache.set(key, data, ttl)
        finally:
            release_lock(key)
    return data

Q13: 缓存与数据库一致性

Cache Aside 模式(推荐): 读流程:
  1. 先读缓存
  2. 缓存命中则返回
  3. 未命中则查 DB,写入缓存
写流程:
  1. 更新 DB
  2. 删除缓存(而非更新)
为什么删除而不是更新?
  • 避免并发写导致缓存和 DB 不一致
  • 延迟更新,下次读取时自动加载
延迟双删:
def update_data(key, value):
    cache.delete(key)         # 第一次删除
    db.update(key, value)
    time.sleep(0.5)           # 等待可能的并发读
    cache.delete(key)         # 第二次删除

Q14: 分布式锁的正确实现

加锁:
SET lock_key unique_value NX PX 30000
# NX: 不存在才设置
# PX: 30 秒过期
解锁(Lua 保证原子性):
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
锁续期(看门狗):
  • 后台线程定期检查锁
  • 业务未完成则延长过期时间
  • Redisson 已内置实现

五、性能优化

Q15: 内存淘汰策略

策略说明适用场景
noeviction不淘汰,写入报错数据不能丢
allkeys-lru淘汰最近最少使用通用缓存
allkeys-lfu淘汰最不常用(Redis 4.0+)热点数据明显
volatile-ttl淘汰快过期的有过期时间的数据
volatile-lru只在设置过期时间的 key 中淘汰部分数据需持久化
推荐: allkeys-lruallkeys-lfu

Q16: 大 key 问题及处理

风险:
  • 阻塞其他请求(单线程)
  • 网络传输慢
  • DEL 可能阻塞主线程
发现大 key:
redis-cli --bigkeys
redis-cli --memkeys
处理方案:
  1. 拆分:大 Hash 拆成多个小 Hash
  2. 异步删除UNLINK 代替 DEL
  3. 分批操作HSCAN/SSCAN 分批读取

Q17: Redis 为什么这么快

因素说明
内存存储直接操作内存,无磁盘 IO
单线程命令处理无线程切换、无锁竞争
IO 多路复用epoll 高效处理并发连接
高效数据结构SDS、跳表、哈希表等
IO 多线程(Redis 6.0+)读写网络 IO 多线程

Q18: Pipeline 和事务的区别

特性Pipeline事务 (MULTI/EXEC)
目的减少网络往返保证原子执行
原子性✅(部分原子)
回滚-❌ 不支持
性能更好略差
注意: Redis 事务不保证 ACID,编译错误会取消事务,运行时错误不回滚。

六、更多八股文

Q19: 过期删除策略

策略说明使用场景
定时删除key 过期立即删除CPU 开销大,不使用
惰性删除访问时检查是否过期Redis 使用
定期删除周期性随机检查删除Redis 使用
Redis 采用: 惰性删除 + 定期删除

Q20: 热点 Key 问题

危害:
  • 单节点压力过大
  • 网络带宽瓶颈
解决方案:
方案说明
本地缓存减少 Redis 访问
读写分离多个从节点分担读压力
二级 key将 key 拆分为多个
热点发现--hotkeys 参数

Q21: Redis Lua 脚本

优点:
  • 原子性执行
  • 减少网络往返
  • 复杂逻辑封装
示例:
-- 限流脚本
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or "0")

if current + 1 > limit then
    return 0
else
    redis.call('INCR', key)
    redis.call('EXPIRE', key, 1)
    return 1
end

Q22: Redlock 分布式锁

原理: 向多个独立 Redis 节点加锁 步骤:
  1. 获取当前时间
  2. 依次向 N 个节点请求加锁
  3. 超过半数成功且耗时小于锁过期时间 → 加锁成功
  4. 失败则释放所有节点的锁
争议: Martin Kleppmann 质疑其正确性(时钟漂移问题)

Q23: Redis 集群扩容流程

# 1. 添加新节点
redis-cli --cluster add-node new_host:port existing_host:port

# 2. 重新分配槽位
redis-cli --cluster reshard existing_host:port

# 3. 数据迁移(自动)

Q24: Redis 和 Memcached 的区别

特性RedisMemcached
数据类型丰富(5 种+)只有 String
持久化
集群✅ 原生支持客户端分片
多线程6.0+ IO 多线程多线程
内存效率略低更高

七、常用命令

Q25: Redis 各数据类型常用命令汇总

分类命令说明
StringSET / GET设置/获取值
INCR / DECR自增/自减
SETNXKey 不存在才设置(分布式锁核心)
SETEX设置值并指定过期时间(秒)
MSET / MGET批量设置/获取
HashHSET / HGET设置/获取字段
HMSET / HMGET批量操作字段
HGETALL获取所有字段和值
HDEL删除字段
ListLPUSH / RPUSH左推/右推入队列
LPOP / RPOP左弹/右弹出队列
LRANGE获取指定范围元素(如 0 -1)
BLPOP / BRPOP阻塞式弹出(消息队列常用)
SetSADD / SREM添加/删除元素
SMEMBERS获取所有元素
SISMEMBER判断元素是否存在
SINTER / SUNION交集 / 并集
ZSetZADD添加元素(需指定 score)
ZRANGE按分数从小到大获取
ZREVRANGE按分数从大到小获取
ZSCORE获取元素分数
全局DEL删除 Key
EXPIRE / TTL设置过期时间 / 查看剩余时间
EXISTS判断 Key 是否存在
KEYS查找所有符合模式的 Key(生产禁用)
SCAN渐进式遍历 Key(推荐)

Q26: KEYS 和 SCAN 的区别

特性KEYS patternSCAN cursor
阻塞性✅ 阻塞主线程❌ 非阻塞,分批进行
执行效率O(N) 一次性返回O(1) 每次返回少量数据
生产环境⚠️ 禁止使用✅ 推荐使用
数据一致性结果准确可能会有重复或漏掉(期间有增删)

八、高频考点清单

必考

  • 五种数据类型及底层实现
  • 常见数据类型命令(SETNX, HGETALL, ZRANGE等)
  • RDB vs AOF,同步策略
  • 缓存穿透/击穿/雪崩解决方案
  • 分布式锁正确实现
  • 主从复制与哨兵选主流程

常考

  • ZSet 为什么用跳表
  • AOF 重写与混合持久化
  • KEYS vs SCAN 的区别
  • Cluster 槽位与 hash tag
  • 内存淘汰策略
  • 大 key 处理
  • 过期删除策略

进阶

  • Redis 单线程为何高性能
  • IO 多路复用原理
  • Raft 选举(Sentinel/Cluster)
  • 缓存与 DB 一致性方案
  • Redlock 原理与争议
  • 热点 Key 解决方案