Redis 题库
50 道精选 Redis 题,涵盖基础到进阶
📋 目录
基础篇
1. Redis 是什么?有什么特点?
答案: Redis 是一个开源的内存数据结构存储系统,可作为数据库、缓存和消息队列。
特点:
- 高性能(10万+ QPS)
- 丰富的数据类型
- 原子性操作
- 持久化支持
- 主从复制、哨兵、集群
2. Redis 为什么这么快?
答案:
- 纯内存操作:数据存储在内存中
- 单线程模型:避免线程切换和锁竞争
- IO 多路复用:epoll/kqueue 高效处理网络 IO
- 高效数据结构:底层使用优化的数据结构
3. Redis 单线程为什么还这么快?
答案:
- 避免上下文切换:无线程切换开销
- 避免锁竞争:无需加锁
- CPU 不是瓶颈:Redis 的瓶颈是内存和网络
- IO 多路复用:高效处理多个连接
注意:Redis 6.0 引入了 IO 多线程,但命令执行仍是单线程。
4. Redis 和 Memcached 的区别?
答案:
| 特性 | Redis | Memcached |
|---|---|---|
| 数据类型 | String、Hash、List、Set、ZSet | 只支持 String |
| 持久化 | RDB、AOF | 不支持 |
| 分布式 | 集群、哨兵 | 客户端分片 |
| 过期策略 | 多种淘汰策略 | LRU |
| 线程模型 | 单线程 + IO 多路复用 | 多线程 |
5. Redis 的应用场景有哪些?
答案:
- 缓存:热点数据缓存
- Session 存储:用户会话
- 计数器:点赞数、浏览量
- 排行榜:游戏排行、热搜
- 消息队列:任务队列、延时队列
- 分布式锁:防止重复提交
- 限流:接口限流、防刷
6. Redis 支持的数据类型有哪些?
答案: 基本类型:
- String:字符串
- Hash:哈希表
- List:列表
- Set:集合
- Sorted Set:有序集合
特殊类型:
- Bitmap:位图
- HyperLogLog:基数统计
- Geo:地理位置
- Stream:数据流
7. Redis 的过期键删除策略?
答案:
-
惰性删除:
- 访问键时检查是否过期
- 优点:节省 CPU
- 缺点:占用内存
-
定期删除:
- 每秒 10 次随机检查过期键
- 优点:平衡 CPU 和内存
-
主动删除:
- 内存不足时触发淘汰策略
8. Redis 的内存淘汰策略有哪些?
答案:
# 8 种淘汰策略
noeviction # 不淘汰,返回错误(默认)
allkeys-lru # 从所有键中淘汰最近最少使用(推荐)
allkeys-lfu # 从所有键中淘汰最不经常使用
allkeys-random # 从所有键中随机淘汰
volatile-lru # 从设置过期时间的键中淘汰 LRU
volatile-lfu # 从设置过期时间的键中淘汰 LFU
volatile-random # 从设置过期时间的键中随机淘汰
volatile-ttl # 淘汰即将过期的键
推荐:allkeys-lru
9. Redis 事务的特点?
答案: 命令:MULTI、EXEC、DISCARD、WATCH
特点:
- 原子性:要么全执行,要么全不执行
- 隔离性:串行执行,不会被打断
- 无回滚:命令错误不会回滚
注意:Redis 事务不支持回滚!
10. Redis 的 Pipeline 是什么?
答案: Pipeline 将多个命令打包一次性发送,减少网络往返。
优点:
- 减少网络延迟
- 提高吞吐量
注意:
- Pipeline 不保证原子性
- 不宜一次性发送太多命令
11. WATCH 命令的作用?
答案: WATCH 用于实现乐观锁。
原理:
- 监视一个或多个键
- 如果键被修改,事务将失败
示例:
WATCH key
GET key
MULTI
SET key newvalue
EXEC # 如果 key 被其他客户端修改,返回 nil
12. Redis 如何实现分布式锁?
答案:
# 加锁
SET lock:order:1 uuid123 NX EX 10
# 解锁(使用 Lua 保证原子性)
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
要点:
- 使用 SET NX EX 加锁
- 设置过期时间防止死锁
- 使用 Lua 脚本释放锁保证原子性
- 锁的值用唯一标识(UUID)
13. Redis 的发布订阅?
答案:
# 订阅频道
SUBSCRIBE channel1
# 发布消息
PUBLISH channel1 "message"
# 模式订阅
PSUBSCRIBE news.*
特点:
- 消息不持久化
- 无消息确认
- 适合实时通知
14. Redis keys 命令为什么不推荐使用?
答案: 原因:
- 单线程阻塞:keys 操作会阻塞 Redis
- O(n) 复杂度:键越多越慢
- 生产环境可能导致服务不可用
替代方案:
# 使用 SCAN 游标迭代
SCAN 0 MATCH pattern COUNT 100
15. Redis 如何实现限流?
答案: 方案 1:固定窗口(INCR + EXPIRE)
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return current <= tonumber(ARGV[2]) and 1 or 0
方案 2:滑动窗口(Sorted Set)
# 添加请求记录
ZADD rate:user:1 timestamp timestamp
# 删除过期记录
ZREMRANGEBYSCORE rate:user:1 0 (timestamp-60000)
# 统计数量
ZCARD rate:user:1
数据类型篇
16. String 的应用场景?
答案:
- 缓存:热点数据
- 计数器:点赞数、浏览量
- Session:用户会话
- 分布式锁:SETNX
- 限流:INCR + EXPIRE
17. Hash 的应用场景?
答案:
- 用户信息:HSET user:1 name "Alice"
- 商品信息:HSET product:101 price 99
- 购物车:HSET cart:user:1 product:101 2
优势:
- 节省内存(相比多个 String)
- 字段独立操作
18. List 的应用场景?
答案:
- 消息队列:LPUSH + BRPOP
- 时间线:微博、朋友圈
- 最新列表:最新文章、最新评论
- 栈:LPUSH + LPOP
- 队列:LPUSH + RPOP
19. Set 的应用场景?
答案:
- 标签系统:文章标签、用户兴趣
- 共同好友:SINTER
- 去重:自动去重
- 抽奖系统:SPOP 随机抽取
- 推荐系统:基于标签推荐
20. Sorted Set 的应用场景?
答案:
- 排行榜:游戏排行、成绩排名
- 热搜榜:按热度排序
- 延时队列:按时间戳排序
- 权重排序:按权重值排序
21. Bitmap 的应用场景?
答案:
- 签到统计:每天一位
- 在线用户统计:10 亿用户 = 119MB
- 活跃用户统计:按天/周/月统计
- 布隆过滤器:判断元素是否存在
22. HyperLogLog 的应用场景?
答案:
- UV 统计:网站独立访客
- 搜索引擎:查询去重
- 大数据:海量数据去重计数
特点:
- 12KB 可统计 2^64 个元素
- 0.81% 误差率
23. Geo 的应用场景?
答案:
- LBS 应用:附近的人、附近的商家
- 打车软件:查找附近的司机
- 外卖平台:查找附近的餐厅
24. Stream 的应用场景?
答案:
- 消息队列:替代 List
- 日志系统:实时日志流
- 事件溯源:事件流存储
优势:
- 消息持久化
- 消费者组
- 消息确认
25. 如何选择合适的数据类型?
答案:
| 需求 | 数据类型 |
|---|---|
| 简单键值 | String |
| 对象存储 | Hash |
| 列表、队列 | List |
| 去重、集合运算 | Set |
| 排行榜 | Sorted Set |
| 签到统计 | Bitmap |
| UV 统计 | HyperLogLog |
| 地理位置 | Geo |
| 消息队列 | Stream |
持久化篇
26. Redis 持久化方式有哪些?
答案:
-
RDB(快照):
- 定期保存内存快照
- 文件小,恢复快
- 可能丢失最后一次快照后的数据
-
AOF(追加文件):
- 记录每个写命令
- 数据更安全(最多丢 1 秒)
- 文件大,恢复慢
-
混合持久化:
- AOF 重写时先写 RDB,再追加增量命令
- 兼具两者优势
27. RDB 和 AOF 如何选择?
答案:
| 场景 | 推荐方案 |
|---|---|
| 数据不能丢 | AOF (everysec) |
| 可接受分钟级数据丢失 | RDB |
| 追求性能 | RDB |
| 兼顾安全和性能 | 混合持久化 |
28. RDB 的触发方式?
答案: 自动触发:
save 900 1 # 900 秒内至少 1 次修改
save 300 10 # 300 秒内至少 10 次修改
save 60 10000 # 60 秒内至少 10000 次修改
手动触发:
SAVE # 同步保存(阻塞)
BGSAVE # 异步保存(fork 子进程)
29. AOF 的同步策略?
答案:
appendfsync always # 每个命令都同步(慢但最安全)
appendfsync everysec # 每秒同步(推荐)
appendfsync no # 由操作系统决定(快但可能丢数据)
推荐:everysec,平衡性能和安全性。
30. AOF 重写是什么?
答案: AOF 文件会越来越大,重写可以压缩 AOF 文件。
原理:
- 遍历内存中的数据
- 生成最少的命令
- 替换原 AOF 文件
触发:
# 手动
BGREWRITEAOF
# 自动
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
高可用篇
31. Redis 主从复制原理?
答案: 全量同步:
- 从节点发送 PSYNC 命令
- 主节点执行 BGSAVE 生成 RDB
- 主节点发送 RDB 文件
- 从节点清空数据并加载 RDB
- 主节点发送增量命令
增量同步:
- 主节点将写命令发送到复制缓冲区
- 从节点接收并执行命令
32. Redis 哨兵的作用?
答案:
- 监控:检查主从节点是否正常
- 通知:故障时通知管理员
- 自动故障转移:主节点故障时自动切换
- 配置提供:客户端获取主节点地址
33. 哨兵如何判断节点下线?
答案: 主观下线(Subjectively Down):
- 单个哨兵认为节点下线
- 超过 down-after-milliseconds 未响应
客观下线(Objectively Down):
- 多数哨兵认为节点下线
- 达到 quorum 数量
34. 哨兵故障转移流程?
答案:
- 主观下线:单个哨兵认为主节点下线
- 客观下线:多数哨兵认为主节点下线
- 选举领导哨兵:Raft 算法选举
- 故障转移:
- 选择一个从节点提升为主节点
- 其他从节点复制新主节点
- 通知客户端新主节点地址
35. Redis 集群如何分片?
答案:
- 16384 个槽位
- CRC16 算法:
slot = CRC16(key) % 16384 - 槽位分配给不同节点
36. Redis 集群如何扩容?
答案:
- 添加新节点
- 重新分配槽位
- 迁移数据
# 添加节点
redis-cli --cluster add-node new_host:port existing_host:port
# 重新分配槽位
redis-cli --cluster reshard existing_host:port
37. Redis 集群的优缺点?
答案: 优点:
- 水平扩展
- 高可用
- 自动故障转移
缺点:
- 不支持多键操作(除非在同一槽位)
- 客户端需要支持集群协议
- 运维复杂度高
38. 如何保证 Redis 高可用?
答案:
- 主从复制:数据备份
- 哨兵模式:自动故障转移
- 集群模式:分片 + 高可用
- 持久化:RDB + AOF
- 监控告警:及时发现问题
39. Redis 脑裂问题?
答案: 脑裂:网络分区导致同时存在多个主节点。
后果:数据不一致、数据丢失。
解决方案:
# 要求至少 1 个从节点在线
min-replicas-to-write 1
# 延迟不超过 10 秒
min-replicas-max-lag 10
40. Redis 如何实现读写分离?
答案:
- 主节点:处理写请求
- 从节点:处理读请求
注意:
- 主从同步有延迟
- 可能读到旧数据
- 适合对一致性要求不高的场景
性能优化篇
41. 什么是大 Key?如何解决?
答案: 大 Key:
- String:值大于 10KB
- Hash/List/Set/ZSet:元素超过 5000
危害:
- 阻塞其他命令
- 内存不均衡
- 删除耗时
解决方案:
- 拆分大 Key
- 使用 UNLINK 异步删除
- 监控大 Key(redis-cli --bigkeys)
42. 什么是热 Key?如何解决?
答案: 热 Key:频繁访问的 Key。
危害:
- 单点压力大
- 可能导致节点崩溃
解决方案:
- 本地缓存
- 多副本(key_replica_1, key_replica_2)
- 读写分离
43. Redis 慢查询如何排查?
答案:
# 查看慢查询
SLOWLOG GET 10
# 配置
CONFIG SET slowlog-log-slower-than 10000 # 10ms
CONFIG SET slowlog-max-len 128
优化:
- 避免 KEYS *,改用 SCAN
- 避免 HGETALL 大 Hash,改用 HSCAN
- 使用 Pipeline 批量操作
44. Redis 内存占用过高如何优化?
答案:
- 设置最大内存:maxmemory 1gb
- 淘汰策略:allkeys-lru
- 压缩数据:使用 Hash 代替 String
- 设置过期时间:EXPIRE
- 清理无用数据:定期清理
45. Redis 如何做性能测试?
答案:
# redis-benchmark
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000
# 参数
-c: 并发连接数
-n: 请求数
-d: 数据大小
-t: 测试命令(SET,GET,INCR等)
实战篇
46. 如何解决缓存穿透?
答案: 问题:查询不存在的数据,缓存和数据库都没有。
解决方案:
- 缓存空值:
data = cache.get(key)
if data is None:
data = db.get(key)
if data is None:
cache.set(key, 'NULL', 60) # 缓存空值
- 布隆过滤器:
if not bloom_filter.exists(key):
return None # 一定不存在
47. 如何解决缓存击穿?
答案: 问题:热点 Key 过期,瞬间大量请求打到数据库。
解决方案:
- 互斥锁:
if cache.get(key) is None:
if lock.acquire():
data = db.get(key)
cache.set(key, data)
lock.release()
- 永不过期(逻辑过期):
data = cache.get(key)
if data['expire'] < now:
async_update_cache(key) # 异步更新
return data['value']
48. 如何解决缓存雪崩?
答案: 问题:大量 Key 同时过期,数据库压力骤增。
解决方案:
- 过期时间加随机值:
expire = 3600 + random.randint(0, 300)
cache.set(key, data, expire)
-
多级缓存:
- Redis + 本地缓存
-
限流降级:
- 数据库限流
- 返回降级数据
49. 如何实现延时队列?
答案: 使用 Sorted Set:
# 添加任务(时间戳作为分数)
ZADD delay:queue 1698048000 "task1"
# 获取到期的任务
ZRANGEBYSCORE delay:queue 0 current_timestamp LIMIT 0 100
# 删除已处理的任务
ZREM delay:queue "task1"
Lua 脚本实现:
local tasks = redis.call('ZRANGEBYSCORE', KEYS[1], 0, ARGV[1], 'LIMIT', 0, 100)
for i, task in ipairs(tasks) do
redis.call('ZREM', KEYS[1], task)
end
return tasks
50. Redis 在项目中遇到过什么问题?
答案: 示例 1:缓存击穿
- 问题:热点商品缓存过期,瞬间大量请求打到数据库
- 解决:使用分布式锁 + 双重检查
示例 2:大 Key
- 问题:单个 Hash 存储 10万条数据,查询慢
- 解决:拆分为多个小 Hash
示例 3:内存不足
- 问题:Redis 内存占用过高
- 解决:设置 maxmemory + allkeys-lru 淘汰策略
总结
Redis 核心:
- ✅ 理解 Redis 原理(单线程、IO 多路复用)
- ✅ 掌握数据类型和应用场景
- ✅ 熟悉持久化机制(RDB、AOF)
- ✅ 了解高可用方案(主从、哨兵、集群)
- ✅ 能解决缓存问题(穿透、击穿、雪崩)
- ✅ 会使用 Lua 脚本
- ✅ 有实际项目经验
建议:
- 理论结合实践
- 举例说明
- 量化指标(QPS、响应时间等)
- 总结踩过的坑
祝你顺利! 🎉