写在前面
经过前面14天的学习,我们已经全面掌握了Redis的核心知识。今天是Redis系列的最后一篇,我们将系统梳理Redis面试高频考点,帮助大家查漏补缺,顺利通过面试。

文章目录
-
- 写在前面
- 一、Redis基础面试题
-
- [1.1 Redis是什么?有什么特点?](#1.1 Redis是什么?有什么特点?)
- [1.2 Redis为什么这么快?](#1.2 Redis为什么这么快?)
- [1.3 Redis单线程为什么还能高并发?](#1.3 Redis单线程为什么还能高并发?)
- [1.4 Redis和Memcached的区别?](#1.4 Redis和Memcached的区别?)
- 二、数据结构面试题
-
- [2.1 Redis有哪些数据类型?各有什么应用场景?](#2.1 Redis有哪些数据类型?各有什么应用场景?)
- [2.2 Redis String的内部编码有哪些?](#2.2 Redis String的内部编码有哪些?)
- [2.3 Redis Hash的底层实现?](#2.3 Redis Hash的底层实现?)
- [2.4 Redis ZSet为什么使用跳表而不是红黑树?](#2.4 Redis ZSet为什么使用跳表而不是红黑树?)
- [2.5 Redis List的底层实现?](#2.5 Redis List的底层实现?)
- 三、持久化面试题
-
- [3.1 Redis持久化机制有哪些?](#3.1 Redis持久化机制有哪些?)
- [3.2 RDB和AOF如何选择?](#3.2 RDB和AOF如何选择?)
- [3.3 AOF重写是什么?](#3.3 AOF重写是什么?)
- [3.4 Redis重启时如何选择恢复方式?](#3.4 Redis重启时如何选择恢复方式?)
- 四、高可用面试题
-
- [4.1 Redis主从复制的原理?](#4.1 Redis主从复制的原理?)
- [4.2 哨兵模式的原理?](#4.2 哨兵模式的原理?)
- [4.3 Redis Cluster的原理?](#4.3 Redis Cluster的原理?)
- [4.4 Cluster和哨兵的区别?](#4.4 Cluster和哨兵的区别?)
- 五、缓存应用面试题
-
- [5.1 什么是缓存穿透、击穿、雪崩?](#5.1 什么是缓存穿透、击穿、雪崩?)
- [5.2 如何保证缓存和数据库一致性?](#5.2 如何保证缓存和数据库一致性?)
- [5.3 为什么是删除缓存而不是更新缓存?](#5.3 为什么是删除缓存而不是更新缓存?)
- 六、分布式锁面试题
-
- [6.1 Redis分布式锁如何实现?](#6.1 Redis分布式锁如何实现?)
- [6.2 Redis分布式锁的可靠性如何保证?](#6.2 Redis分布式锁的可靠性如何保证?)
- [6.3 Redis分布式锁和Zookeeper分布式锁的区别?](#6.3 Redis分布式锁和Zookeeper分布式锁的区别?)
- 七、性能优化面试题
-
- [7.1 如何排查Redis性能问题?](#7.1 如何排查Redis性能问题?)
- [7.2 如何优化Redis内存使用?](#7.2 如何优化Redis内存使用?)
- [7.3 为什么KEYS命令在生产环境禁用?](#7.3 为什么KEYS命令在生产环境禁用?)
- 八、其他高频面试题
-
- [8.1 Redis如何实现消息队列?](#8.1 Redis如何实现消息队列?)
- [8.2 Redis如何实现延迟队列?](#8.2 Redis如何实现延迟队列?)
- [8.3 Redis如何实现限流?](#8.3 Redis如何实现限流?)
- [8.4 Redis如何实现分布式Session?](#8.4 Redis如何实现分布式Session?)
- [8.5 Redis内存满了怎么办?](#8.5 Redis内存满了怎么办?)
- 九、学习路线总结
-
- [9.1 Redis学习路线图](#9.1 Redis学习路线图)
- [9.2 面试重点总结](#9.2 面试重点总结)
- 十、参考资料
- 十一、互动话题
一、Redis基础面试题
1.1 Redis是什么?有什么特点?
答案:
Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值对数据库。
主要特点:
| 特点 | 说明 |
|---|---|
| 高性能 | 基于内存,读写速度极快(10万+QPS) |
| 丰富数据类型 | String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、Geo、Stream |
| 持久化 | RDB和AOF两种持久化方式 |
| 高可用 | 主从复制、哨兵模式、Cluster集群 |
| 单线程 | 核心处理流程采用单线程模型 |
| 支持事务 | 提供事务支持,保证原子性 |
1.2 Redis为什么这么快?
答案:
| 原因 | 说明 |
|---|---|
| 基于内存 | 数据存储在内存中,读写速度极快 |
| 单线程模型 | 避免多线程上下文切换开销 |
| IO多路复用 | 使用epoll实现高并发连接处理 |
| 高效数据结构 | 使用跳表、压缩列表等高效数据结构 |
| 协议简单 | RESP协议简单高效 |
详细解释:
-
单线程模型:Redis核心处理流程采用单线程,避免了多线程的锁竞争和上下文切换开销。
-
IO多路复用:Redis使用epoll(Linux)实现IO多路复用,单线程可以处理大量并发连接。
-
高效数据结构:
- 字符串使用SDS(Simple Dynamic String)
- 列表使用quicklist
- 有序集合使用跳表
- 小数据量使用压缩列表
1.3 Redis单线程为什么还能高并发?
答案:
-
纯内存操作:内存访问速度极快,纳秒级
-
非阻塞IO:使用epoll实现IO多路复用,单线程处理多个连接
-
避免锁竞争:单线程不需要加锁,避免了锁开销
-
CPU亲和性:单线程可以充分利用CPU缓存
注意:Redis 6.0引入了多线程,用于网络IO处理,核心命令执行仍然是单线程。
1.4 Redis和Memcached的区别?
答案:
| 对比项 | Redis | Memcached |
|---|---|---|
| 数据类型 | 丰富(5种基础+扩展) | 仅支持String |
| 持久化 | 支持RDB/AOF | 不支持 |
| 集群 | 原生支持Cluster | 需要客户端分片 |
| 事务 | 支持 | 不支持 |
| 内存管理 | 更灵活 | 更简单 |
| 性能 | 高 | 高(略快于Redis) |
| 适用场景 | 缓存+数据存储 | 纯缓存 |
二、数据结构面试题
2.1 Redis有哪些数据类型?各有什么应用场景?
答案:
| 数据类型 | 底层实现 | 应用场景 |
|---|---|---|
| String | SDS | 缓存、计数器、分布式锁 |
| Hash | ziplist/hashtable | 对象存储、购物车 |
| List | quicklist | 消息队列、文章列表 |
| Set | intset/hashtable | 标签、共同关注 |
| ZSet | skiplist+dict | 排行榜、延时队列 |
| Bitmap | String | 签到、布隆过滤器 |
| HyperLogLog | String | 基数统计 |
| Geo | ZSet | 地理位置 |
| Stream | listpack | 消息队列 |
2.2 Redis String的内部编码有哪些?
答案:
| 编码 | 条件 | 特点 |
|---|---|---|
| int | 整数值且范围在long内 | 内存占用最小 |
| embstr | 字符串长度≤44字节 | 一次分配,紧凑存储 |
| raw | 字符串长度>44字节 | SDS实现,可修改 |
查看编码:
redis
OBJECT ENCODING mykey
2.3 Redis Hash的底层实现?
答案:
Redis Hash底层使用两种编码:
-
ziplist(压缩列表):
- 条件:元素数≤512,所有值长度≤64字节
- 特点:内存紧凑,适合小数据量
- 时间复杂度:O(N)
-
hashtable(哈希表):
- 条件:不满足ziplist条件
- 特点:O(1)时间复杂度
- 扩容:负载因子>1时扩容
2.4 Redis ZSet为什么使用跳表而不是红黑树?
答案:
| 对比项 | 跳表 | 红黑树 |
|---|---|---|
| 实现复杂度 | 简单 | 复杂 |
| 范围查询 | 高效 | 需要中序遍历 |
| 内存占用 | 略高 | 较低 |
| 并发友好 | 更好 | 一般 |
| 插入删除 | 简单 | 需要旋转调整 |
Redis选择跳表的原因:
-
实现简单:跳表比红黑树更容易实现和维护
-
范围查询高效:跳表在找到起点后,可以顺序遍历,效率更高
-
内存效率:虽然跳表内存略高,但Redis主要瓶颈不在内存
2.5 Redis List的底层实现?
答案:
Redis 3.2之后,List使用quicklist(快速列表)实现:
- quicklist是ziplist的双向链表
- 结合了ziplist的内存紧凑和链表的快速插入删除
- 可以配置每个ziplist的大小和压缩策略
conf
# 配置参数
list-max-ziplist-size -2 # 每个ziplist大小
list-compress-depth 0 # 压缩深度
三、持久化面试题
3.1 Redis持久化机制有哪些?
答案:
| 机制 | RDB | AOF |
|---|---|---|
| 方式 | 快照 | 日志追加 |
| 文件大小 | 小 | 大 |
| 恢复速度 | 快 | 慢 |
| 数据安全性 | 可能丢失 | 更安全 |
| 系统资源 | CPU消耗大 | IO消耗大 |
| 适用场景 | 备份、主从复制 | 数据安全要求高 |
3.2 RDB和AOF如何选择?
答案:
推荐方案:同时开启RDB和AOF
| 场景 | 推荐方案 |
|---|---|
| 数据安全要求高 | 开启AOF |
| 允许少量数据丢失 | 只开启RDB |
| 生产环境 | 同时开启RDB+AOF |
| 纯缓存场景 | 可不持久化 |
配置建议:
conf
# RDB配置
save 900 1
save 300 10
save 60 10000
# AOF配置
appendonly yes
appendfsync everysec
3.3 AOF重写是什么?
答案:
AOF重写原理:
- 随着写操作增加,AOF文件越来越大
- 重写会创建一个新的AOF文件,只保留最终数据的写入命令
- 重写过程中不影响正常服务
重写触发条件:
conf
# AOF文件大小超过上次重写后大小的100%
auto-aof-rewrite-percentage 100
# AOF文件最小64MB才触发重写
auto-aof-rewrite-min-size 64mb
重写流程:
- Redis创建子进程进行重写
- 子进程根据内存数据生成新的AOF文件
- 父进程继续处理命令,写入AOF重写缓冲区
- 子进程完成后,父进程将重写缓冲区追加到新文件
- 用新文件替换旧AOF文件
3.4 Redis重启时如何选择恢复方式?
答案:
Redis启动时恢复数据的优先级:
- 如果开启AOF,优先使用AOF恢复
- 如果只开启RDB,使用RDB恢复
- 如果AOF文件损坏,可以使用redis-check-aof修复
shell
# 修复AOF文件
redis-check-aof --fix appendonly.aof
# 检查RDB文件
redis-check-rdb dump.rdb
四、高可用面试题
4.1 Redis主从复制的原理?
答案:
主从复制流程:
从节点连接主节点
↓
发送SYNC命令
↓
主节点执行BGSAVE生成RDB
↓
主节点发送RDB给从节点
↓
从节点加载RDB
↓
主节点发送缓冲区写命令
↓
进入增量复制阶段
复制方式:
| 方式 | 说明 |
|---|---|
| 全量复制 | 第一次同步或断开时间较长 |
| 增量复制 | 断开后重连,只同步差异部分 |
| 异步复制 | 主节点写完立即返回,异步同步 |
4.2 哨兵模式的原理?
答案:
哨兵功能:
- 监控:持续检查主从节点是否正常
- 通知:节点故障时通知管理员或其他应用
- 自动故障转移:主节点故障时自动选举新主节点
故障转移流程:
哨兵检测主节点下线(主观下线)
↓
多个哨兵确认(客观下线)
↓
哨兵选举领导者
↓
领导者选举新主节点
↓
更新其他从节点的复制目标
↓
通知客户端新主节点地址
配置示例:
conf
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
4.3 Redis Cluster的原理?
答案:
核心概念:
-
哈希槽:将数据划分为16384个槽,分配给不同节点
-
数据分片:每个节点负责一部分槽
-
Gossip协议:节点间通信,交换状态信息
槽位计算:
slot = CRC16(key) % 16384
MOVED重定向:
当客户端访问的key不在当前节点时,返回MOVED错误,告知正确的节点。
4.4 Cluster和哨兵的区别?
答案:
| 对比项 | Cluster | 哨兵 |
|---|---|---|
| 数据分片 | 支持 | 不支持 |
| 高可用 | 支持 | 支持 |
| 最小节点数 | 6个 | 3个 |
| 扩展性 | 支持在线扩容 | 不支持 |
| 客户端 | 需要集群客户端 | 普通客户端 |
| 复杂度 | 较高 | 较低 |
| 适用场景 | 大数据量 | 小数据量 |
五、缓存应用面试题
5.1 什么是缓存穿透、击穿、雪崩?
答案:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的数据 | 布隆过滤器、空值缓存 |
| 缓存击穿 | 热点key过期 | 互斥锁、热点预热 |
| 缓存雪崩 | 大量key同时过期 | 随机过期时间、多级缓存 |
详细解答:
缓存穿透:
- 现象:查询不存在的key,请求穿透到数据库
- 解决:布隆过滤器过滤不存在的key,或缓存空值
缓存击穿:
- 现象:热点key过期瞬间,大量请求打到数据库
- 解决:加互斥锁,只允许一个线程查询数据库
缓存雪崩:
- 现象:大量key同时过期,或Redis宕机
- 解决:随机过期时间、多级缓存、熔断降级
5.2 如何保证缓存和数据库一致性?
答案:
常用方案:Cache Aside模式
java
// 更新数据
public void update(String key, Object value) {
// 1. 先更新数据库
db.update(key, value);
// 2. 再删除缓存
cache.delete(key);
}
// 读取数据
public Object get(String key) {
// 1. 先查缓存
Object value = cache.get(key);
if (value != null) {
return value;
}
// 2. 查数据库
value = db.query(key);
// 3. 写入缓存
if (value != null) {
cache.set(key, value, expire);
}
return value;
}
延迟双删:
java
public void update(String key, Object value) {
// 1. 删除缓存
cache.delete(key);
// 2. 更新数据库
db.update(key, value);
// 3. 延迟后再次删除缓存
Thread.sleep(500);
cache.delete(key);
}
5.3 为什么是删除缓存而不是更新缓存?
答案:
| 对比项 | 删除缓存 | 更新缓存 |
|---|---|---|
| 复杂度 | 低 | 高 |
| 性能 | 高(懒加载) | 低(每次写都更新) |
| 一致性 | 较好 | 可能不一致 |
| 并发问题 | 较少 | 较多 |
原因分析:
-
并发安全:删除是幂等操作,更新可能被覆盖
-
性能考虑:很多场景缓存可能不会被读取
-
数据新鲜:删除后下次读取时加载最新数据
六、分布式锁面试题
6.1 Redis分布式锁如何实现?
答案:
基本实现:
redis
# 加锁
SET lock:key value NX PX 30000
# 解锁(Lua脚本保证原子性)
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
Java实现:
java
public boolean lock(String key, String value, long expireTime) {
return redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
}
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";
Long result = redisTemplate.execute(
RedisScript.of(script, Long.class),
Collections.singletonList(key), value);
return result != null && result == 1L;
}
6.2 Redis分布式锁的可靠性如何保证?
答案:
| 问题 | 解决方案 |
|---|---|
| 死锁 | 设置过期时间 |
| 误删锁 | 使用唯一标识校验 |
| 锁超时 | 看门狗自动续期 |
| 单点故障 | Redlock算法 |
| 主从切换丢锁 | 多节点部署 |
Redisson实现:
java
RLock lock = redisson.getLock("myLock");
try {
lock.lock(); // 自动续期
// 执行业务
} finally {
lock.unlock();
}
6.3 Redis分布式锁和Zookeeper分布式锁的区别?
答案:
| 对比项 | Redis | Zookeeper |
|---|---|---|
| 实现方式 | SET NX + 过期时间 | 临时顺序节点 |
| 一致性 | 最终一致 | 强一致 |
| 性能 | 高 | 中 |
| 锁释放 | 主动删除或过期 | Session断开自动释放 |
| 可重入 | 需要自己实现 | 天然支持 |
| 适用场景 | 高并发 | 高可靠 |
七、性能优化面试题
7.1 如何排查Redis性能问题?
答案:
排查步骤:
- 查看慢查询日志
redis
SLOWLOG GET 10
- 检查内存使用
redis
INFO memory
- 检查大key
shell
redis-cli --bigkeys
- 检查客户端连接
redis
CLIENT LIST
- 实时监控
redis
MONITOR
7.2 如何优化Redis内存使用?
答案:
| 优化方法 | 说明 |
|---|---|
| 选择合适的数据结构 | 使用编码效率高的类型 |
| 控制key长度 | 使用简短的key名 |
| 使用压缩编码 | ziplist、intset |
| 设置过期时间 | 及时清理无用数据 |
| 开启内存碎片整理 | activedefrag |
| 避免大key | 拆分大key |
7.3 为什么KEYS命令在生产环境禁用?
答案:
-
时间复杂度O(N):需要遍历所有key
-
阻塞主线程:Redis单线程,KEYS执行期间无法处理其他请求
-
影响可用性:大量key时可能导致服务不可用
替代方案:使用SCAN命令
redis
SCAN 0 MATCH user:* COUNT 100
八、其他高频面试题
8.1 Redis如何实现消息队列?
答案:
| 方案 | 实现 | 特点 |
|---|---|---|
| List | LPUSH + BRPOP | 简单,不支持消费组 |
| Pub/Sub | PUBLISH + SUBSCRIBE | 实时,不持久化 |
| Stream | XADD + XREAD | 支持消费组,可持久化 |
Stream实现:
redis
# 生产消息
XADD mystream * field1 value1
# 消费消息
XREAD GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
8.2 Redis如何实现延迟队列?
答案:
使用ZSet实现延迟队列:
java
// 添加延迟任务
public void addDelayTask(String taskId, long delayTime) {
long executeTime = System.currentTimeMillis() + delayTime;
redisTemplate.opsForZSet().add("delay:queue", taskId, executeTime);
}
// 消费延迟任务
public void consumeDelayTask() {
while (true) {
long now = System.currentTimeMillis();
Set<String> tasks = redisTemplate.opsForZSet()
.rangeByScore("delay:queue", 0, now, 0, 10);
for (String taskId : tasks) {
// 尝试获取任务
Long removed = redisTemplate.opsForZSet()
.remove("delay:queue", taskId);
if (removed > 0) {
// 执行任务
executeTask(taskId);
}
}
Thread.sleep(100);
}
}
8.3 Redis如何实现限流?
答案:
方案1:计数器限流
java
public boolean allowRequest(String key, int limit, long period) {
long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, period, TimeUnit.SECONDS);
}
return count <= limit;
}
方案2:滑动窗口限流
java
public boolean allowRequest(String key, int limit, long period) {
long now = System.currentTimeMillis();
long start = now - period * 1000;
// 移除过期请求
redisTemplate.opsForZSet().removeRangeByScore(key, 0, start);
// 统计当前窗口请求数
long count = redisTemplate.opsForZSet().count(key, start, now);
if (count < limit) {
redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), now);
return true;
}
return false;
}
方案3:令牌桶限流
lua
-- Lua脚本实现令牌桶
local tokens = redis.call('get', KEYS[1])
if tokens == false then
tokens = ARGV[1]
end
if tonumber(tokens) > 0 then
redis.call('decr', KEYS[1])
return 1
else
return 0
end
8.4 Redis如何实现分布式Session?
答案:
java
// 存储Session
public void setSession(String sessionId, Object data) {
String key = "session:" + sessionId;
redisTemplate.opsForValue().set(key, data, 30, TimeUnit.MINUTES);
}
// 获取Session
public Object getSession(String sessionId) {
String key = "session:" + sessionId;
return redisTemplate.opsForValue().get(key);
}
// 删除Session
public void deleteSession(String sessionId) {
String key = "session:" + sessionId;
redisTemplate.delete(key);
}
8.5 Redis内存满了怎么办?
答案:
内存淘汰策略:
| 策略 | 说明 |
|---|---|
| noeviction | 不淘汰,写入报错 |
| allkeys-lru | 所有key使用LRU淘汰 |
| volatile-lru | 设置过期时间的key使用LRU淘汰 |
| allkeys-lfu | 所有key使用LFU淘汰 |
| volatile-lfu | 设置过期时间的key使用LFU淘汰 |
| allkeys-random | 所有key随机淘汰 |
| volatile-random | 设置过期时间的key随机淘汰 |
| volatile-ttl | 淘汰即将过期的key |
配置:
conf
maxmemory 4gb
maxmemory-policy allkeys-lru
九、学习路线总结
9.1 Redis学习路线图
基础阶段
├── Redis简介与安装
├── 数据类型与命令
├── 持久化(RDB/AOF)
└── 主从复制
进阶阶段
├── 哨兵模式
├── Cluster集群
├── 缓存应用(穿透/击穿/雪崩)
└── 分布式锁
高级阶段
├── 性能优化
├── 内存管理
├── 监控运维
└── 源码分析
9.2 面试重点总结
| 知识点 | 重要程度 | 考察频率 |
|---|---|---|
| 数据类型与应用场景 | ★★★★★ | 必考 |
| 持久化机制 | ★★★★★ | 必考 |
| 主从复制原理 | ★★★★☆ | 高频 |
| 哨兵模式 | ★★★★☆ | 高频 |
| Cluster集群 | ★★★★☆ | 高频 |
| 缓存三兄弟 | ★★★★★ | 必考 |
| 分布式锁 | ★★★★★ | 必考 |
| 性能优化 | ★★★★☆ | 高频 |
十、参考资料
十一、互动话题
恭喜你完成了Redis系列的学习!🎉
- 这个系列对你有帮助吗?欢迎反馈学习心得!
- 面试中遇到过哪些Redis问题?欢迎分享!
- 还想学习哪些技术?欢迎留言建议!
Redis系列文章完结撒花!感谢大家的陪伴!