Redis 面试题详解(2026面试突击)

📅 发布时间:2026/7/30 5:49:22
Redis 面试题详解(2026面试突击)
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包含以下几种情况:类型标准典型场景Stringvalue 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 Freelazyfree-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计数器StringUV统计HyperLogLog用户签到Bitmap地理位置GEO(3)序列化格式选择JSON:通用但体积大MessagePack / Protobuf:体积小,但可读性差根据场景权衡体积和兼容性(4)设置合理的过期时间避免内存无限增长,根据业务设置合适的TTL。(5)避免大Key和热Key遵循上文的大Key和热Key解决方案。7. Redis如何高效安全的遍历所有keyKEYS命令的问题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 表示结束各类型的SCANSCAN # 遍历所有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线上操作最佳实践有哪些命令使用规范危险操作替代方案KEYS *SCAN分批遍历FLUSHALL/FLUSHDB改名禁用(rename-command)DEL bigkeyUNLINK异步删除HGETALL bigkeyHSCAN分批获取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实例组)