一、数据结构
Q1: Redis 的五种基本数据类型及底层实现
| 类型 | 底层实现 | 应用场景 |
|---|---|---|
| String | SDS(动态字符串) | 缓存、计数器、分布式锁 |
| Hash | ziplist / hashtable | 对象属性存储 |
| List | quicklist(ziplist + 链表) | 消息队列、时间线 |
| Set | intset / hashtable | 标签、共同好友 |
| ZSet | ziplist / skiplist + hashtable | 排行榜、优先队列 |
Q2: 为什么用 SDS 而不是 C 字符串?
| 特性 | C 字符串 | SDS |
|---|---|---|
| 获取长度 | O(N) 遍历 | O(1) 直接读 len |
| 缓冲区溢出 | 可能溢出 | 自动扩容 |
| 二进制安全 | ❌ 不支持 \0 | ✅ 支持任意二进制 |
| 内存分配 | 每次修改都分配 | 空间预分配、惰性释放 |
Q3: ZSet 为什么用跳表而不是红黑树?
| 特性 | 跳表 | 红黑树 |
|---|---|---|
| 实现复杂度 | 简单 | 复杂 |
| 范围查询 | ✅ O(logN + M) | 需要中序遍历 |
| 内存占用 | 略多 | 略少 |
| 并发友好 | 更容易加锁 | 旋转操作复杂 |
Q4: 常用的扩展数据类型
| 类型 | 说明 | 应用场景 |
|---|---|---|
| Bitmap | 位图 | 用户签到、在线状态 |
| HyperLogLog | 基数统计(误差 0.81%) | UV 统计 |
| Geo | 地理位置 | 附近的人、门店 |
| Stream | 消息流 | 消息队列(Redis 5.0+) |
二、持久化
Q5: RDB 和 AOF 的区别
| 特性 | RDB | AOF |
|---|---|---|
| 原理 | 内存快照 | 命令日志 |
| 文件大小 | 小(二进制压缩) | 大(文本命令) |
| 恢复速度 | 快 | 慢 |
| 数据安全 | 可能丢失分钟级数据 | 最多丢 1 秒 |
| 性能影响 | fork 子进程时影响 | everysec 影响小 |
Q6: AOF 的同步策略
everysec,最多丢失 1 秒数据。
Q7: AOF 重写机制
为什么需要重写?- AOF 文件持续增长
- 很多命令可以合并(如多次 INCR 合并为 SET)
- fork 子进程,读取当前内存数据
- 子进程写入新 AOF 文件
- 主进程继续处理命令,增量写入 AOF 重写缓冲区
- 子进程完成后,将缓冲区内容追加到新文件
- 原子替换旧 AOF 文件
Q8: 混合持久化
原理(Redis 4.0+):- AOF 重写时,先写入 RDB 格式快照
- 快照后的增量命令以 AOF 格式追加
- 恢复速度快(RDB 部分)
- 数据安全性高(AOF 部分)
三、集群与高可用
Q9: 主从复制原理
全量复制:- Slave 发送 PSYNC 命令
- Master 执行 BGSAVE,生成 RDB
- Master 发送 RDB 给 Slave
- Master 发送复制缓冲区内容
- 使用
repl_backlog环形缓冲区 - Slave 断线重连后,发送 offset
- 如果 offset 在 backlog 内,只同步增量
Q10: 哨兵模式的选主流程
1. 主观下线(SDOWN):- Sentinel 认为 Master 不可用
- 超过 quorum 个 Sentinel 都认为 Master 不可用
- 过滤不健康的 Slave
- 按优先级
slave-priority排序 - 优先级相同,选复制偏移量最大的
- 偏移量相同,选 runid 最小的
Q11: Cluster 模式的特点
槽位分配:- 固定 16384 个槽位
- 数据按
CRC16(key) % 16384分配到槽
- 多 key 命令(MGET、MSET)必须在同一槽
- 使用 hash tag:
{user}:1001和{user}:1002在同一槽
四、缓存问题
Q12: 缓存穿透、击穿、雪崩
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 穿透 | 查询不存在的数据 | 布隆过滤器、缓存空值 |
| 击穿 | 热点 key 过期 | 互斥锁、永不过期 + 异步更新 |
| 雪崩 | 大量 key 同时过期 | 随机过期时间、多级缓存 |
Q13: 缓存与数据库一致性
Cache Aside 模式(推荐): 读流程:- 先读缓存
- 缓存命中则返回
- 未命中则查 DB,写入缓存
- 更新 DB
- 删除缓存(而非更新)
- 避免并发写导致缓存和 DB 不一致
- 延迟更新,下次读取时自动加载
Q14: 分布式锁的正确实现
加锁:- 后台线程定期检查锁
- 业务未完成则延长过期时间
- Redisson 已内置实现
五、性能优化
Q15: 内存淘汰策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| noeviction | 不淘汰,写入报错 | 数据不能丢 |
| allkeys-lru | 淘汰最近最少使用 | 通用缓存 |
| allkeys-lfu | 淘汰最不常用(Redis 4.0+) | 热点数据明显 |
| volatile-ttl | 淘汰快过期的 | 有过期时间的数据 |
| volatile-lru | 只在设置过期时间的 key 中淘汰 | 部分数据需持久化 |
allkeys-lru 或 allkeys-lfu
Q16: 大 key 问题及处理
风险:- 阻塞其他请求(单线程)
- 网络传输慢
- DEL 可能阻塞主线程
- 拆分:大 Hash 拆成多个小 Hash
- 异步删除:
UNLINK代替DEL - 分批操作:
HSCAN/SSCAN分批读取
Q17: Redis 为什么这么快
| 因素 | 说明 |
|---|---|
| 内存存储 | 直接操作内存,无磁盘 IO |
| 单线程命令处理 | 无线程切换、无锁竞争 |
| IO 多路复用 | epoll 高效处理并发连接 |
| 高效数据结构 | SDS、跳表、哈希表等 |
| IO 多线程(Redis 6.0+) | 读写网络 IO 多线程 |
Q18: Pipeline 和事务的区别
| 特性 | Pipeline | 事务 (MULTI/EXEC) |
|---|---|---|
| 目的 | 减少网络往返 | 保证原子执行 |
| 原子性 | ❌ | ✅(部分原子) |
| 回滚 | - | ❌ 不支持 |
| 性能 | 更好 | 略差 |
六、更多八股文
Q19: 过期删除策略
| 策略 | 说明 | 使用场景 |
|---|---|---|
| 定时删除 | key 过期立即删除 | CPU 开销大,不使用 |
| 惰性删除 | 访问时检查是否过期 | Redis 使用 |
| 定期删除 | 周期性随机检查删除 | Redis 使用 |
Q20: 热点 Key 问题
危害:- 单节点压力过大
- 网络带宽瓶颈
| 方案 | 说明 |
|---|---|
| 本地缓存 | 减少 Redis 访问 |
| 读写分离 | 多个从节点分担读压力 |
| 二级 key | 将 key 拆分为多个 |
| 热点发现 | --hotkeys 参数 |
Q21: Redis Lua 脚本
优点:- 原子性执行
- 减少网络往返
- 复杂逻辑封装
Q22: Redlock 分布式锁
原理: 向多个独立 Redis 节点加锁 步骤:- 获取当前时间
- 依次向 N 个节点请求加锁
- 超过半数成功且耗时小于锁过期时间 → 加锁成功
- 失败则释放所有节点的锁
Q23: Redis 集群扩容流程
Q24: Redis 和 Memcached 的区别
| 特性 | Redis | Memcached |
|---|---|---|
| 数据类型 | 丰富(5 种+) | 只有 String |
| 持久化 | ✅ | ❌ |
| 集群 | ✅ 原生支持 | 客户端分片 |
| 多线程 | 6.0+ IO 多线程 | 多线程 |
| 内存效率 | 略低 | 更高 |
七、常用命令
Q25: Redis 各数据类型常用命令汇总
| 分类 | 命令 | 说明 |
|---|---|---|
| String | SET / GET | 设置/获取值 |
INCR / DECR | 自增/自减 | |
SETNX | Key 不存在才设置(分布式锁核心) | |
SETEX | 设置值并指定过期时间(秒) | |
MSET / MGET | 批量设置/获取 | |
| Hash | HSET / HGET | 设置/获取字段 |
HMSET / HMGET | 批量操作字段 | |
HGETALL | 获取所有字段和值 | |
HDEL | 删除字段 | |
| List | LPUSH / RPUSH | 左推/右推入队列 |
LPOP / RPOP | 左弹/右弹出队列 | |
LRANGE | 获取指定范围元素(如 0 -1) | |
BLPOP / BRPOP | 阻塞式弹出(消息队列常用) | |
| Set | SADD / SREM | 添加/删除元素 |
SMEMBERS | 获取所有元素 | |
SISMEMBER | 判断元素是否存在 | |
SINTER / SUNION | 交集 / 并集 | |
| ZSet | ZADD | 添加元素(需指定 score) |
ZRANGE | 按分数从小到大获取 | |
ZREVRANGE | 按分数从大到小获取 | |
ZSCORE | 获取元素分数 | |
| 全局 | DEL | 删除 Key |
EXPIRE / TTL | 设置过期时间 / 查看剩余时间 | |
EXISTS | 判断 Key 是否存在 | |
KEYS | 查找所有符合模式的 Key(生产禁用) | |
SCAN | 渐进式遍历 Key(推荐) |
Q26: KEYS 和 SCAN 的区别
| 特性 | KEYS pattern | SCAN 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 解决方案