Redis 面试题详解(2026面试突击)
2026/7/30 5:49:13 网站建设 项目流程

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 yesio-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耗时增加。
  • 慢查询HGETALLSMEMBERSLRANGE 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-evictionlazyfree-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 # 遍历ZSet
COUNT参数说明
  • 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: break

8. Redis线上操作最佳实践有哪些

命令使用规范

危险操作

替代方案

KEYS *

SCAN分批遍历

FLUSHALL/FLUSHDB

改名禁用(rename-command)

DEL bigkey

UNLINK异步删除

HGETALL bigkey

HSCAN分批获取

CONFIG SET

限制使用,配置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实例组)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询