文章目录
一、Redis为什么是面试必考题?🔥
作为互联网公司的"标配缓存中间件",Redis的面试出现率高达87%!(数据来自拉勾2023统计)特别是在高并发场景下,Redis就像系统的"速效救心丸",没它分分钟系统崩溃。今天咱们就掰开揉碎了讲透Redis面试高频考点,看完保你面试官直呼内行!
二、数据结构灵魂拷问篇
1. Redis为什么把字符串叫SDS?
面试官最爱问:“知道C语言字符串吧?Redis的字符串和它有什么区别?”
这里藏着Redis的第一个设计哲学!传统C字符串有三大致命伤:
- 获取长度要遍历(O(n)时间复杂度)
- 容易缓冲区溢出
- 只能存文本
Redis的SDS(Simple Dynamic String)结构直接解决所有痛点:
struct sdshdr {
int len; // 已用字节数
int free; // 未用字节数
char buf[]; // 字节数组
};
(敲黑板)关键优势:
- O(1)获取字符串长度
- 自动扩容防溢出(预分配策略)
- 二进制安全(可以存图片等二进制数据)
2. Zset底层怎么同时支持排序和快速查询?
死亡追问:“跳表这么低效的结构,Redis为什么还要用?”
先看Zset的双引擎结构:
- 跳表(SkipList)负责范围查询
- 哈希表(Dict)负责单键查询
举个栗子🌰:当执行ZRANGE salary 0 -1 WITHSCORES
时:
- 跳表的层结构快速定位范围起点
- 沿着最底层链表遍历获取范围数据
而执行ZSCORE user123
时:
- 哈希表O(1)时间复杂度直接命中
(划重点)跳表在Redis中的神优化:
- 最大层数限制为32层(足够支撑2^64元素)
- 随机层数生成算法(越高的层数概率越小)
- 每个节点包含多个前进指针和跨度值
三、持久化机制终极之战
3. AOF重写会阻塞主线程吗?
陷阱题警告:“都说Redis是单线程,那AOF重写会不会导致服务不可用?”
这里涉及Redis的"影分身之术"——fork操作:
- 主进程fork出子进程(copy-on-write)
- 子进程遍历内存生成新AOF文件
- 期间主进程继续处理命令(同时记录到AOF缓冲区)
- 重写完成后追加缓冲区内容
(避坑指南)可能出现的阻塞情况:
- fork瞬间内存过大导致阻塞(20GB内存约200ms)
- 机械硬盘写入速度跟不上
- AOF重写缓冲区溢出
4. RDB和AOF混合模式怎么选?
2024年最新实践方案:
- 线上环境建议开启混合模式(Redis4.0+)
- RDB做全量备份(例如每天1次)
- AOF做增量备份(每秒刷盘)
- 灾难恢复时先加载RDB再重放AOF
配置示例:
save 900 1
save 300 10
aof-use-rdb-preamble yes
四、集群方案灵魂三问
5. 数据分片怎么保证均匀分布?
当面试官抛出:“你们Redis集群的hot key问题怎么解决的?”
分四个层次回答:
- 基础层:哈希槽分配(16384个slot)
- 协议层:MOVED/ASK重定向机制
- 客户端层:SmartClient缓存路由表
- 业务层:对热点key添加随机后缀(例:cart:123 -> cart:123_{1…N})
(实战技巧)使用redis-cli检查槽分配:
redis-cli --cluster check 127.0.0.1:7000
6. 脑裂问题怎么破?
CAP理论在Redis集群中的具象化体现,解决方案三部曲:
- 配置min-slaves-to-write(至少写入N个从节点)
- 设置合理的cluster-node-timeout(建议10-15秒)
- 部署哨兵集群的quorum值要大于一半
五、缓存问题处理指南
7. 缓存雪崩 vs 击穿 vs 穿透
用一张表格说清区别:
雪崩 | 击穿 | 穿透 | |
---|---|---|---|
触发条件 | 大量key同时失效 | 单个热点key失效 | 查询不存在的数据 |
解决方案 | 随机过期时间 | 永不过期+互斥锁 | 布隆过滤器 |
防御等级 | 集群部署+本地缓存 | 多级缓存 | 参数校验 |
(血泪教训)布隆过滤器的坑:
- 误判率计算(公式:
(1-e^(-kn/m))^k
) - 不支持删除操作(考虑Counting Bloom Filter)
- 空间估算(100万元素/1%误判率约需958KB)
六、实战编程题精选
8. 用Redis实现分布式锁
2024年最新正确姿势:
-- 加锁脚本
local key = KEYS[1]
local value = ARGV[1]
local ttl = ARGV[2]
if redis.call('setnx', key, value) == 1 then
redis.call('pexpire', key, ttl)
return 1
else
-- 检查是否是自己加的锁
if redis.call('get', key) == value then
redis.call('pexpire', key, ttl)
return 1
end
end
return 0
(避坑提示)一定要用随机值做value!防止误解锁。推荐使用Redisson框架的看门狗机制。
七、高频扩展问题清单
- Redis6.0多线程模型改变了什么?(IO多线程,命令执行仍单线程)
- Stream类型怎么实现消息队列?(对比Kafka的设计)
- 大Key删除的正确姿势?(UNLINK命令+lazyfree配置)
- Redis怎么实现延迟队列?(SortedSet+轮询)
- Pipeline和事务的区别?(Pipeline是批量发送,事务是原子执行)
八、性能优化冷知识
- 使用HSCAN代替HGETALL(防止阻塞)
- 集合元素数量控制在1万以内(超过后编码方式改变)
- 禁用KEYS命令(用SCAN替代)
- 合理设置maxmemory-policy(推荐volatile-lru)
最后的话
Redis就像瑞士军刀,功能多但要用对场景。建议大家在本地搭建集群环境,用redis-benchmark
做压测,真实感受不同配置下的性能差异。记住:技术没有银弹,Redis也不是万能的,但它绝对是你应对高并发场景的核武器!