Redis 面试题详解(2026面试突击)
1. Redis 6为什么引入了多线程
Redis 6之前一直是单线程模型(指命令执行和数据操作部分),但随着网络硬件的发展,单个Redis实例可以轻松处理数万个客户端连接,网络I/O成为了瓶颈——大量时间消耗在socket的read/write系统调用上。
Redis 6的多线程设计
Redis 6引入的多线程仅限于网络I/O层面:
- 命令执行仍然是单线程:所有对数据的读写操作依然由主线程串行处理,保证了操作的原子性,避免了并发控制的复杂性。
- I/O线程负责网络数据的读写:解析请求协议、发送响应数据这些工作可以交给多个I/O线程并行处理。
- 默认关闭:需要通过
io-threads-do-reads yes和io-threads 4等配置开启。
为什么之前不引入
- Redis的瓶颈主要是内存和网络带宽,CPU很少成为瓶颈。
- Redis 5及之前版本,单线程足够快,引入多线程反而增加了复杂度。
- 到了Redis 6,万兆网卡普及,网络I/O压力显著增加,才真正需要多线程来分担。
线程模型示意
客户端请求 → I/O线程池(多线程解析协议、读数据) ↓ 主线程(单线程执行命令,操作内存数据) ↓ I/O线程池(多线程写响应数据) → 返回客户端核心要点:Redis 6是"I/O多线程"而非"命令执行多线程",保证了数据操作的原子性和简单性。
2. Redis的热Key问题如何解决
什么是热Key
某个key被大量请求集中访问,导致单个Redis节点CPU/带宽达到瓶颈,可能影响整个集群的稳定性和响应时间。
解决方案
(1)Key拆分(分片思想)
将热点key拆分成多个子key,分散到不同节点:
原始:hot:item:1001 拆分:hot:item:1001:0, hot:item:1001:1, hot:item:1001:2 ...客户端随机或按用户ID哈希到不同子key上。
(2)本地缓存(二级缓存)
在应用服务器本地加一层缓存(Caffeine/Guava/Ehcache),热点数据直接从本地内存获取,大幅减少对Redis的访问。
请求 → 本地缓存(Caffeine) → 命中则直接返回 ↓(未命中) Redis → 回写本地缓存(3)读写分离
为热点key所在节点增加多个只读副本(slave),读请求分发到不同副本上。但写请求仍然是瓶颈,需要配合其他方案。
(4)限流与降级
对热点key的访问进行限流(如每秒最多处理N个请求),超限的请求直接降级返回默认值或空值。
(5)Redis Cluster自动分散
利用Redis Cluster的hash slot机制,使用不同的key前缀让数据天然分散到不同节点。
3. Redis的大Key问题如何解决
什么是大Key
大Key包含以下几种情况:
类型 | 标准 | 典型场景 |
String | value > 10KB | 存储大JSON/序列化对象 |
Hash/Set/ZSet/List | 元素数量 > 10000 | 大量用户关注列表、订单集合 |
大Key的危害
- 阻塞主线程:
DEL大key时Redis是单线程执行,会阻塞其他请求。 - 内存不均匀:集群中各节点内存分布不均衡。
- 网络带宽压力:频繁读取大key消耗大量带宽。
- 影响主从同步/持久化:同步、RDB/AOF时大key导致fork耗时增加。
- 慢查询:
HGETALL、SMEMBERS、LRANGE 0 -1等操作非常耗时。
如何发现大Key
# Redis自带的大key扫描(Redis 4.0+,建议从节点执行) redis-cli --bigkeys # 使用memory usage查看具体key的内存占用 MEMORY USAGE keyname # 扫描各类型key的元素数量 DEBUG OBJECT keyname解决方案
(1)拆分为小Key
# 原始:一个大hash存储所有用户属性 user:1001 → {name, age, email, phone, addr, ...} # 拆分:按属性维度拆分 user:1001:base → {name, age} user:1001:contact → {email, phone} user:1001:extra → {addr, ...}List/ZSet按时间或ID分段拆分:list:key:1,list:key:2...
(2)使用UNLINK代替DEL(Redis 4.0+)
UNLINK将删除操作放到后台线程异步执行,不阻塞主线程。
UNLINK bigkey # 异步删除,不阻塞(3)避免危险命令
- 用
HSCAN/SSCAN/ZSCAN代替HGETALL/SMEMBERS/ZRANGE 0 -1 - 删除Hash时用
HDEL分批删除,配合HSCAN遍历 - ZSet用
ZREMRANGEBYRANK分批删除
(4)数据压缩
对大String进行压缩(如Snappy、Gzip)后再存入Redis,读取时解压。
(5)改用更适合的存储
对于特别大的数据,考虑用对象存储(OSS/S3),Redis只存URL引用。
4. 缓存与数据库双写不一致问题如何解决
问题场景
先更新数据库,再删除缓存(Cache-Aside模式下的经典问题):
- 先删缓存再更新DB:删缓存后、DB更新前,另一个请求读DB写入旧数据到缓存。
- 先更新DB再删缓存:DB更新后、删缓存前,另一个请求读到旧缓存数据。
解决方案
(1)Cache-Aside + 延迟双删(最常用)
1. 删除缓存 2. 更新数据库 3. 延迟N毫秒(如500ms) 4. 再次删除缓存延迟双删能覆盖绝大部分并发场景的不一致问题。
(2)先更新DB,再删除缓存(推荐)
1. 更新数据库 2. 删除缓存(而非更新缓存)为什么是删除而非更新?因为更新缓存可能覆盖并发写入,而删除缓存后下次读会从DB加载最新数据。
(3)基于Binlog的异步更新(Canal)
MySQL Binlog → Canal监听 → 解析变更 → 异步更新/删除Redis优点:与业务代码解耦,最终一致性。缺点:有一定延迟。
(4)设置合理的过期时间
作为兜底方案,即使出现不一致,TTL过期后也会自动修复。
(5)分布式读写锁
对同一数据的读写加分布式锁,保证读写操作的串行化。但会降低并发性能。
(6)MQ异步重试
删除缓存失败时发送MQ消息,异步重试直到成功。
实际项目中:通常采用"先更新DB再删缓存 + 设置合理TTL + MQ重试"的组合方案,既保证性能又保证最终一致性。
5. Redis中key过期了一定会立即删除吗
答:不会立即删除。Redis采用三种过期键删除策略的组合:
三种删除策略
(1)惰性删除(Lazy Expiration)
当客户端访问一个key时,Redis先检查该key是否过期,如果过期则删除并返回空。
- 优点:对CPU友好,只在访问时检查。
- 缺点:如果过期key一直未被访问,会一直占用内存(内存泄漏风险)。
(2)定期删除(Active/Periodic Expiration)
Redis每100ms(由hz参数控制)执行一次定期删除任务:
- 从设置了过期时间的key中随机抽取20个
- 删除其中已过期的key
- 如果过期key比例超过25%,则重复此过程
- 为避免阻塞,每次总执行时间不超过25ms(默认)
- 优点:弥补惰性删除的缺陷,主动清理过期key。
- 缺点:随机抽样,可能漏掉部分过期key。
(3)内存淘汰策略兜底
当Redis内存达到maxmemory上限时,根据配置的淘汰策略(如allkeys-lru)强制删除部分key。
Redis 4.0+ 的改进:Lazy Free
lazyfree-lazy-eviction和lazyfree-lazy-expire配置项开启后,过期删除的实际释放操作交由后台线程异步执行,避免阻塞主线程。
结论:Redis的过期key删除是"惰性+定期+淘汰"的混合策略,不保证立即删除,可能短时间内存中存在已过期但未删除的key。
6. Redis的Key和Value的设计原则有哪些
Key设计原则
(1)可读性
# 好的命名 user:1001:profile order:20240101:status # 不好的命名 u:1001:p o:20240101:s(2)简洁但不失语义
不要过长(会占用更多内存),但要能表达含义。
(3)使用冒号分隔(命名空间)
业务:实体:属性的层级结构,便于管理和分组。
(4)避免使用特殊字符
避免空格、换行、中文key(虽然支持但不推荐),使用ASCII字符。
(5)统一命名规范
团队内统一前缀和命名规则,例如:
cache:— 纯缓存数据lock:— 分布式锁counter:— 计数器queue:— 消息队列
(6)Key不宜过大
key本身也是占内存的,建议不超过1024字节。
Value设计原则
(1)控制Value大小
- String类型建议不超过10KB
- 集合类型元素数量建议不超过10000
(2)选择合适的数据结构
场景 | 推荐结构 |
对象缓存 | Hash(可部分更新,节省内存) |
排行榜 | ZSet |
去重集合 | Set |
消息队列 | List / Stream |
计数器 | String |
UV统计 | HyperLogLog |
用户签到 | Bitmap |
地理位置 | GEO |
(3)序列化格式选择
- JSON:通用但体积大
- MessagePack / Protobuf:体积小,但可读性差
- 根据场景权衡体积和兼容性
(4)设置合理的过期时间
避免内存无限增长,根据业务设置合适的TTL。
(5)避免大Key和热Key
遵循上文的大Key和热Key解决方案。
7. Redis如何高效安全的遍历所有key
KEYS命令的问题
KEYS pattern # ❌ 生产环境禁用!- 阻塞主线程:KEYS是O(N)操作,会遍历所有key,期间Redis无法处理其他请求。
- 大量数据时极其危险:可能造成几秒甚至几十秒的阻塞,导致连接超时、雪崩。
正确方案:SCAN命令(游标迭代)
# 基本用法 SCAN cursor [MATCH pattern] [COUNT count]SCAN的特点
- 非阻塞:基于游标(cursor)分批迭代,每次只返回少量key,不阻塞主线程。
- 增量迭代:每次返回新的cursor,cursor=0时表示遍历完成。
- 不保证完整性:遍历过程中增删的key可能被返回0次或多次(不会漏掉稳定key)。
- 可能返回重复:客户端需要自行去重。
使用示例
# 第一次扫描 SCAN 0 MATCH user:* COUNT 100 # 返回:cursor=1024, keys=[user:1, user:3, ...] # 继续扫描 SCAN 1024 MATCH user:* COUNT 100 # 返回:cursor=0, keys=[user:5, user:8, ...] -- cursor=0 表示结束各类型的SCAN
SCAN # 遍历所有key SSCAN # 遍历Set HSCAN # 遍历Hash ZSCAN # 遍历ZSetCOUNT参数说明
COUNT只是一个提示(hint),不是精确返回数量。- 每次返回的key数量大约为COUNT值,但可能多也可能少。
生产环境遍历最佳实践
# Python伪代码示例 cursor = 0 while True: cursor, keys = redis.scan(cursor, match='user:*', count=100) for key in keys: # 处理key(如检查类型、TTL、大小等) process_key(key) if cursor == 0: break8. Redis线上操作最佳实践有哪些
命令使用规范
危险操作 | 替代方案 |
|
|
| 改名禁用(rename-command) |
|
|
|
|
| 限制使用,配置rename |
配置建议
# 禁用危险命令 rename-command FLUSHALL "" rename-command FLUSHDB "" rename-command KEYS "" rename-command CONFIG "CONFIG_b9fc8321" # 内存限制 maxmemory 4gb maxmemory-policy allkeys-lru # 慢日志 slowlog-log-slower-than 10000 # 超过10ms记录 slowlog-max-len 128 # 连接限制 timeout 300 # 空闲连接超时 maxclients 10000 tcp-keepalive 300 # 持久化 save 900 1 save 300 10 save 60 10000日常运维
- 备份:定期RDB备份,使用
BGSAVE避免阻塞。 - 监控:监控内存使用率、命中率、慢查询、连接数、QPS等关键指标。
- 容量规划:预估数据增长,提前扩容。
- 大Key扫描:定期执行
--bigkeys检查并优化。 - 客户端规范:使用连接池(JedisPool/Lettuce),设置合理的超时和重试策略。
安全建议
- 设置密码(
requirepass),禁止外网访问。 - 绑定内网IP(
bind),限制访问来源。 - 使用
protected-mode yes。 - 禁止以root用户运行Redis。
9. 说一下你知道的Redis高可用方案
(1)主从复制 + Sentinel(哨兵模式)
Sentinel集群(奇数个) │ ┌─────────┼─────────┐ ▼ ▼ ▼ ┌──────┐ ┌──────┐ ┌──────┐ │Master│──│Slave1│──│Slave2│ └──────┘ └──────┘ └──────┘- 工作原理:Sentinel监控Master,Master宕机时自动选举新Master并切换。
- 优点:自动故障转移,读写分离,部署简单。
- 缺点:不解决单机内存/写入瓶颈,切换期间短暂不可用。
(2)Redis Cluster(官方集群方案)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Master A │ │ Master B │ │ Master C │ │ (Slot 0-5460)│ │(Slot 5461- │ │(Slot 10923- │ │ │ │ 10922) │ │ 16383) │ │ Slave A1,A2 │ │ Slave B1,B2 │ │ Slave C1,C2 │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └──── Gossip协议通信 ─────────────┘- 工作原理:16384个hash slot分布在多个Master节点,每个Master可以有多个Slave。
- 优点:水平扩展、自动分片、自动故障转移、无中心化。
- 缺点:客户端需要支持cluster协议,跨slot操作受限(不支持多key事务、mget等)。
(3)Codis(代理层方案)
Client → Codis-Proxy(无状态) → Codis-Server(Redis实例组)