news 2026/10/11 1:46:49

精选 21道 Redis 最常问面试题!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
精选 21道 Redis 最常问面试题!

Redis 作为后端开发面试中的高频考点,几乎每一次 Java、Go、Python、Node.js 岗位面试都会涉及。它不仅是缓存组件,更在分布式锁、消息队列、排行榜、计数器、限流等场景中发挥着重要作用。本文精选了 21 道 Redis 最常问的面试题,覆盖基础概念、底层数据结构、持久化、高可用、缓存一致性、分布式锁等核心方向。

学习路径:定位与性能来源 → 数据类型与底层实现 → 持久化与高可用 → 缓存一致性与分布式锁。


第一部分:基础篇

1. 什么是 Redis?它有哪些优缺点?

Redis(Remote Dictionary Server)是一个基于内存的高性能键值存储系统,由 Salvatore Sanfilippo 使用 C 语言编写,支持字符串、哈希、列表、集合、有序集合等数据结构,并提供持久化、主从复制、哨兵、集群等能力。

核心定位:以内存为主要存储介质、以单线程事件循环为核心工作模型的远程数据结构服务器。它不是单纯的缓存,而是可以承担数据存储职责的内存数据库。

优点:

  • 性能极高:基于内存读写,单机 QPS 可达 10 万级别。

  • 数据结构丰富:list、hash、set、zset、bitmap、hyperloglog、geo、stream 等。

  • 功能全面:持久化、过期策略、发布订阅、事务、Lua 脚本、分布式锁等。

  • 生态成熟:几乎所有主流语言都有客户端。

  • 高可用易扩展:主从复制、哨兵、集群。

缺点:

  • 内存成本高:数据全部放内存,大容量存储成本远高于磁盘数据库。

  • 数据一致性较弱:持久化是异步的,极端情况可能丢失最近数据。

  • 单线程瓶颈:6.0 之前单个耗时命令会阻塞整个实例。

  • 不支持复杂查询:无法进行多条件复杂查询和事务级强一致。

一句话总结:Redis 适合高并发、低延迟、数据量可控的场景,不适合作为海量数据的主存储。


2. Redis 为什么这么快?

不能只说「因为它是内存数据库」,要从多个维度展开:

  1. 基于内存存储:内存随机访问是纳秒级,磁盘是毫秒级,相差几个数量级。

  2. 高效的数据结构:SDS、跳跃表、压缩列表等,查询复杂度经过精心优化。

  3. 单线程事件驱动模型:避免多线程切换和锁竞争,命令执行不涉及 CPU 密集计算。

  4. IO 多路复用:封装 epoll、kqueue、select,一个线程监听成百上千个 socket。

  5. 高效的通信协议:自定义 RESP 协议,解析简单高效。

特别说明:Redis 6.0 引入的多线程只针对网络 IO 读写,命令执行仍然是单线程。


3. Redis 有哪些数据类型?各自的适用场景是什么?

数据类型特点典型场景
String最基本类型,值可以是字符串、整数、浮点数,最大 512MB缓存对象、计数器、分布式锁、Session 共享
Hash键值对集合,适合存储对象属性用户信息、购物车、商品详情
List有序字符串链表,支持两端操作消息队列、最新动态、时间线
Set无序且不重复,支持交并差运算共同好友、标签、抽奖去重
ZSet有序集合,每个元素带分数排序排行榜、延迟队列、按时间排序列表

进阶类型:Bitmap(签到统计、在线状态)、HyperLogLog(UV 统计,有误差)、GEO(附近的人)、Stream(可靠消息队列)、Bitfield(位域操作)。

计数器示例:

shell

127.0.0.1:6379> SET article:1001:views 0 OK 127.0.0.1:6379> INCR article:1001:views (integer) 1 127.0.0.1:6379> INCRBY article:1001:views 10 (integer) 11 127.0.0.1:6379> GET article:1001:views "11"

排行榜示例:

shell

127.0.0.1:6379> ZADD rank 100 user:a 95 user:b 120 user:c (integer) 3 127.0.0.1:6379> ZREVRANGE rank 0 -1 WITHSCORES 1) "user:c" 2) "120" 3) "user:a" 4) "100" 5) "user:b" 6) "95"

4. Redis 底层数据结构有哪些?

Redis 对外类型和底层实现之间有一个「编码」的映射关系,同一对外类型在不同条件下可能使用不同的底层结构。

底层结构特点
SDS(简单动态字符串)O(1) 获取长度,空间预分配 + 惰性释放,String 默认实现
双向链表(LinkedList)插入删除快,指针开销大,早期 List 实现
压缩列表(ZipList)内存连续、占用小,插入删除可能连锁更新
快速列表(QuickList)多个压缩列表 + 双向链表,List 默认实现
哈希表(Dict)链式哈希解决冲突,渐进式 rehash,Hash 和全局键空间核心
整数集合(IntSet)只存整数且元素少时 Set 的编码
跳跃表(SkipList)多层有序链表,平均 O(logN),ZSet 主要实现
ListPackRedis 7.0 引入,替代 ZipList 解决连锁更新

查看编码:

shell

127.0.0.1:6379> SET num 12345 OK 127.0.0.1:6379> OBJECT ENCODING num "int" 127.0.0.1:6379> SET str "hello redis" OK 127.0.0.1:6379> OBJECT ENCODING str "embstr" 127.0.0.1:6379> SET longstr "这是一段超过 44 字节的字符串..." OK 127.0.0.1:6379> OBJECT ENCODING longstr "raw"

理解意义:数据量或元素大小变化时,编码会转换。例如 Hash 元素数量超阈值后从压缩列表转换为哈希表。


5. Redis 的过期键删除策略是什么?

Redis 结合三种策略:

  1. 惰性删除(Lazy Expire):客户端访问键时检查是否过期,过期则删除。对 CPU 友好,但过期键无人访问时长期占用内存。

  2. 定期删除(Active Expire Cycle):周期性主动扫描,随机抽取部分过期键检查,删除已过期的。过期键比例超过 25% 则继续循环。

  3. 内存淘汰机制兜底:内存达到 maxmemory 上限时,按淘汰策略清理。

验证惰性删除行为:

shell

127.0.0.1:6379> SET session:123 token-abc EX 10 OK 127.0.0.1:6379> TTL session:123 (integer) 9 127.0.0.1:6379> GET session:123 "token-abc" # 10 秒后再次访问 127.0.0.1:6379> TTL session:123 (integer) -2 127.0.0.1:6379> GET session:123 (nil)

注意:惰性删除 + 定期删除仍可能造成过期键暂时残留内存,内存敏感业务需合理设置过期时间和 maxmemory 淘汰策略。


6. Redis 的内存淘汰策略有哪些?

策略含义
noeviction不淘汰任何键,写入命令报错(默认)
volatile-lru设置了过期时间的键中淘汰最近最少使用
volatile-lfu设置了过期时间的键中淘汰最近最不常用
volatile-random设置了过期时间的键中随机淘汰
volatile-ttl设置了过期时间的键中淘汰即将过期
allkeys-lru所有键中淘汰最近最少使用
allkeys-lfu所有键中淘汰最近最不常用
allkeys-random所有键中随机淘汰

LRU vs LFU:

  • LRU关注「最近是否被使用」,很久没被访问即使曾高频也会被淘汰。

  • LFU关注「被使用频率」,淘汰访问次数低的键,防止偶发访问占用热度。

  • Redis 的 LRU 是近似实现,通过抽样计算兼顾性能和准确度。

选择经验:

  • 纯缓存场景:allkeys-lru或allkeys-lfu。

  • 既有缓存又有需持久保留的业务数据:volatile-lru或volatile-lfu。

shell

# 查看当前淘汰策略 127.0.0.1:6379> CONFIG GET maxmemory-policy 1) "maxmemory-policy" 2) "noeviction" # 设置为 allkeys-lru 127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru OK # 设置最大内存为 1GB 127.0.0.1:6379> CONFIG SET maxmemory 1gb OK

第二部分:持久化篇

7. Redis 持久化机制 RDB 和 AOF 有什么区别?

RDB 持久化是快照式的,将某一时刻的内存数据以二进制形式保存到磁盘。

  • 触发方式:SAVE(阻塞主进程,生产禁用)、BGSAVE(fork 子进程,主进程继续)、save配置自动触发。

  • 优点:文件紧凑、恢复速度快,适合灾难恢复。

  • 缺点:两次快照之间的数据可能丢失;fork 子进程在数据量大时会短暂阻塞。

AOF 持久化是追加式的,将每条写命令以文本形式记录到 AOF 文件。

  • 三种同步策略:

    • always:每条命令都同步,安全性最高,性能最差。

    • everysec:每秒同步一次,最多丢一秒数据(推荐)。

    • no:由操作系统决定,性能最好,安全性最低。

  • 优点:数据丢失少、文件可读。

  • 缺点:文件体积大、恢复速度慢。

shell

# 查看持久化相关配置 127.0.0.1:6379> CONFIG GET save 1) "save" 2) "3600 1 300 100 60 10000" 127.0.0.1:6379> CONFIG GET appendonly 1) "appendonly" 2) "no" 127.0.0.1:6379> CONFIG GET appendfsync 1) "appendfsync" 2) "everysec"

上面的 save 配置表示:3600 秒内有 1 次修改、300 秒内有 100 次修改、60 秒内有 10000 次修改,满足任一条件就触发 BGSAVE。

核心区别对比:

对比维度RDBAOF
文件内容某个时刻内存数据的二进制快照记录所有写命令的文本格式
数据完整性可能丢失最近一次快照之后的数据最多丢失 1 秒(everysec 策略)
恢复速度快,直接加载即可恢复慢,需要逐条回放命令
文件体积紧凑、占用较小持续增长、体积通常更大
可读性二进制不可读文本可读、必要时可人工修改
资源占用fork 子进程会有瞬时 CPU 和内存开销追加写开销较低,但需要定期 AOF 重写

生产建议:RDB 与 AOF 同时开启,RDB 作为冷备和灾难恢复文件,AOF 作为数据恢复兜底。Redis 4.0 之后支持混合持久化,AOF 重写时前半部分使用 RDB 格式、后半部分追加增量命令,兼顾恢复速度和数据完整性。


第三部分:高可用与集群篇

8. Redis 主从复制的原理是什么?有什么优缺点?

主从复制是将一台 Redis 主节点数据复制到一台或多台从节点。核心价值:数据冗余备份、读写分离、为高可用方案提供基础。

复制流程:

  1. 建立连接:从节点执行replicaof命令,与主节点建立连接并发送同步请求。

  2. 全量同步:主节点执行BGSAVE生成 RDB 快照发送给从节点,同时将生成快照期间的写命令写入复制缓冲区,随后一并发给从节点。

  3. 增量同步:后续主节点通过replication offset记录复制偏移量,将新增写命令持续发送给从节点。

  4. 断线重连:从节点断线后重连,先尝试基于 offset 进行部分同步,不够用才退化为全量同步。

shell

# 在从节点执行,使其复制 127.0.0.1:6379 127.0.0.1:6380> REPLICAOF 127.0.0.1 6379 OK 127.0.0.1:6380> INFO replication # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up

优点:配置简单、读写分离效果明显。

缺点:复制是异步的,从节点数据可能延迟;主从切换时可能丢失少量数据;主节点自身仍是单点,宕机后需人工介入或依赖哨兵自动切换。


9. Redis 哨兵模式是什么?如何实现自动故障转移?

哨兵(Sentinel)是对主从复制架构的增强,本身不存储业务数据,以独立进程运行,负责监控主从节点状态并在主节点故障时自动完成主从切换。

四项职责:监控节点是否存活、发现故障后通知、自动完成故障转移、在客户端询问时返回当前主节点地址。

故障转移关键过程:

  1. 主观下线(SDOWN):单个哨兵发现主节点一段时间内无响应。

  2. 客观下线(ODOWN):多个哨兵达成一致,确认主节点已下线。

  3. 选举领导者:哨兵之间通过投票选出领导者负责本次故障转移。

  4. 选择新主:在健康从节点中按优先级、复制偏移量等因素选择最合适者提升为新主。

  5. 通知与收敛:其他从节点改为复制新主,客户端重新获取新主地址。

conf

# sentinel.conf 配置示例 sentinel monitor mymaster 127.0.0.1 6379 2 sentinel auth-pass mymaster yourpassword sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000

配置中的 2 表示至少需要 2 个哨兵认为主节点下线,才触发客观下线和故障转移。生产环境推荐至少 3 个哨兵节点,尽量部署在不同物理机上。


10. Redis Cluster 集群的原理?数据如何分布?

当单机内存或写入能力成为瓶颈时,需要 Redis Cluster 进行水平扩展。集群由多个主节点组成,每个主节点负责整体数据的一部分,并通常为每个主节点配置从节点提供冗余。集群节点之间通过 Gossip 协议交换状态,不需要中心协调节点。

Redis Cluster 使用哈希槽实现数据分片,共 16384 个槽。每个 key 通过公式CRC16(key) % 16384计算所属槽位,每个主节点负责一部分槽。客户端可连接任意节点,如果请求的 key 不属于该节点,节点返回 MOVED 重定向。

哈希槽方案比一致性哈希更简单,也便于在线迁移。扩容时只需将部分槽从旧节点迁移到新节点:

shell

# 查看集群节点和槽位分布 127.0.0.1:6379> CLUSTER NODES # 查看某个 key 属于哪个槽 127.0.0.1:6379> CLUSTER KEYSLOT order:1001 (integer) 14577

优势:数据自动分片、支持在线扩容。

缺点:很多多 key 操作受槽位限制,跨槽的 mget、事务执行比较困难,需要使用 hash tag 将相关 key 落到同一个槽。


11. Redis 集群中如何定位某个 key 所在的节点?

定位 key 分三步:

  1. 对 key 计算 CRC16 校验和。

  2. 对 16384 取模得到槽号。

  3. 根据槽与节点的映射关系找到目标节点。

实际使用时客户端会缓存槽位映射表,直连目标节点减少重定向。

两种重定向:

  • MOVED:槽位稳定迁移时节点返回,告诉客户端正确的目标节点和槽,客户端更新本地映射。

  • ASK:槽位正在迁移过程中,表示该 key 暂时位于源节点或目标节点,此时只能临时访问指定节点,不应更新长期映射。

Hash tag可以让多个 key 落到同一槽:

shell

# 两个 key 都会使用 user:1001 计算槽位 127.0.0.1:6379> MSET {user:1001}:name tom {user:1001}:age 20 OK 127.0.0.1:6379> MGET {user:1001}:name {user:1001}:age 1) "tom" 2) "20"

注意:hash tag 使用不当会导致数据倾斜,让大量 key 集中到少数槽和节点上。


12. Redis 主从切换过程中数据会丢失吗?如何减少丢失?

会。主从复制是异步的,主节点写入成功后立即给客户端返回 OK,此时数据可能还没同步到从节点。如果主节点突然宕机,这部分未同步的数据就可能丢失。此外,网络分区时哨兵提升的新主与旧主数据不一致,旧主恢复后被降级为从节点时可能覆盖数据,这就是典型的脑裂问题。

减少丢失的措施:

  1. 配置 min-replicas-to-write:要求至少 N 个从节点在线且延迟小于指定秒数时,主节点才接受写命令。

  2. 使用 WAIT 命令:关键写操作后主动等待指定数量的从节点确认同步,相当于把异步复制变成半同步。

  3. 部署偶数哨兵并合理设置 quorum:降低脑裂后选主错误的概率。

  4. 使用 AOF 持久化:即使主从数据不一致,也可以从 AOF 中恢复更多数据。

shell

# 要求至少 1 个从节点在线且延迟不超过 10 秒才允许写入 127.0.0.1:6379> CONFIG SET min-replicas-to-write 1 OK 127.0.0.1:6379> CONFIG SET min-replicas-max-lag 10 OK

明确一点:无论怎么配置,异步复制都无法做到严格不丢失。业务要求强一致时,需要在写入层增加额外确认机制或采用主从同步回执方案。


第四部分:缓存与一致性篇

13. 什么是缓存穿透、缓存击穿、缓存雪崩?如何解决?

三者都可能导致大量请求绕过缓存直接打到数据库,但成因不同,解决思路也不一样。

缓存穿透:查询一个缓存和数据库中都不存在的数据。

  • 解决:对不存在的数据缓存空值并设置较短过期时间;使用布隆过滤器快速判断 key 是否可能存在。

缓存击穿:某个热点 key 在过期的一瞬间,大量并发请求同时穿透缓存打到数据库。

  • 解决:热点数据设置逻辑过期;重建缓存时加互斥锁,只允许一个请求回源查询,其他请求等待或返回旧值。

缓存雪崩:大量 key 在同一时间段集中过期,或整个 Redis 实例宕机。

  • 解决:过期时间加随机值、多级缓存、部署主从与哨兵保证高可用、限流降级。

给缓存空值防穿透:

shell

# 查询不存在的 key 时写入空值 127.0.0.1:6379> GET user:999999 (nil) 127.0.0.1:6379> SET user:999999 "" EX 60 OK

给过期时间加随机值防雪崩:基础过期时间 + 随机秒数,例如60 + random(0, 30),让缓存过期时间分散开。


14. 如何保证缓存与数据库的数据一致性?

缓存与数据库之间天然存在时间差,绝对一致几乎无法做到,工程上更多追求最终一致。

最常用的策略是 Cache Aside 模式:

  • 读请求:先查缓存,命中直接返回,未命中则查数据库并回填缓存。

  • 写请求:先更新数据库,再删除缓存,而不是更新缓存。

为什么推荐删缓存而不是更新缓存?很多场景下缓存的值需要经过计算或组装,直接更新不仅容易产生脏数据,还可能导致大量无用的缓存写入。删除缓存后,下一次读取会自动回源重建,逻辑更简单。

先更新数据库再删缓存的顺序是主流做法,但极端情况下仍可能出现短暂不一致。例如 A 更新数据库后、删除缓存前,B 读到旧缓存。更稳妥的方案是延迟双删:先删缓存,更新数据库,延迟一段时间后再删一次,覆盖并发场景。也可以借助 Canal 等工具监听数据库 binlog,异步驱动缓存失效,实现最终一致。

Cache Aside 读流程伪代码:

text

# value = cache.get(key) # if value is None: # value = db.query(key) # cache.set(key, value, ttl) # return value

如果业务能接受短暂不一致,删除缓存方案已经足够;若要求接近强一致,可考虑分布式事务、可重试的可靠消息或订阅 binlog 的异步补偿机制。


15. Redis 分布式锁如何实现?有哪些坑?

分布式锁的核心目标是在多个进程之间实现互斥访问。基于 Redis 实现时,最基础的做法是使用SET key value NX PX time原子命令:

  • NX:只有 key 不存在时才写入。

  • PX:设置毫秒级过期时间。

  • value:使用当前线程或请求的唯一标识,防止误删其他客户端的锁。

解锁必须小心,不能直接执行DEL,否则可能误删他人已获取的锁。正确做法是使用 Lua 脚本先比较 value 是否一致,再原子删除:

lua

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

常见的坑:

  • 锁没有设置过期时间导致死锁。

  • 业务执行时间超过锁过期时间导致锁提前释放。

  • 使用非原子操作释放导致误删。

  • 主从切换时锁信息未同步造成锁失效。

生产环境一般使用Redisson等成熟客户端,它提供了看门狗续期、可重入锁和公平锁等能力,避免手动实现出问题。

shell

# 加锁,token 为唯一标识,30 秒过期 127.0.0.1:6379> SET lock:order:1001 token-123 NX PX 30000 OK # 使用 Lua 脚本原子解锁 127.0.0.1:6379> EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock:order:1001 token-123 (integer) 1

16. RedLock 算法靠谱吗?

RedLock 算法由 Redis 作者提出,核心思路是:向独立部署的多个 Redis 主节点依次尝试加锁,当在大多数节点加锁成功,并且加锁总耗时小于锁的有效时间时,认为获取锁成功;释放时向所有节点发送删除命令。

它的出发点是用多个独立节点降低单点故障风险。但该算法一直存在争议,马丁·克莱普曼等多位分布式系统专家曾指出,RedLock 依赖的是时钟而不是真正单调的版本号或租约,在 GC 停顿、网络延迟、进程暂停等情况下,仍可能出现多个客户端同时认为自己持有锁的情况,无法提供真正可靠的安全保证。

因此在实践中:

  • 需要较高互斥保证时,更推荐使用基于强一致系统的方案,例如Zookeeper、etcd提供的分布式锁,或使用数据库唯一约束、乐观锁。

  • 只是防止重复提交、限制告警频率等弱一致性场景,单 Redis 节点加锁 + 合理过期时间通常已经足够。

一句话总结:RedLock 在特定场景下可用,但它不是银弹,不能把它当作严格正确的分布式一致性原语。


第五部分:高频面试速查表

主题核心要点
Redis 定位内存数据库,非单纯缓存,单线程事件循环 + IO 多路复用
为什么快内存、高效数据结构、单线程、IO 多路复用、RESP 协议
数据类型String、Hash、List、Set、ZSet + Bitmap、HyperLogLog、GEO、Stream
底层结构SDS、QuickList、Dict、IntSet、SkipList、ListPack
过期删除惰性删除 + 定期删除 + 内存淘汰兜底
内存淘汰noeviction、allkeys-lru/lfu/random、volatile-lru/lfu/random/ttl
RDB快照,恢复快,可能丢最近数据,fork 子进程
AOF追加写命令,everysec 最多丢 1 秒,文件大恢复慢
主从复制全量 + 增量,异步复制,读写在主从分离
哨兵监控 + 自动故障转移 + 通知 + 配置提供者
Cluster16384 个哈希槽,CRC16 取模,MOVED/ASK 重定向,hash tag
缓存三大问题穿透(布隆/空值)、击穿(互斥锁/逻辑过期)、雪崩(随机过期/多级缓存)
缓存一致性Cache Aside,先更新数据库再删缓存,延迟双删,binlog 订阅
分布式锁SET NX PX + Lua 解锁,Redisson 看门狗,RedLock 有争议

总结

Redis 面试的核心主线可以浓缩为:

  1. 基础:定位、性能来源、数据类型、底层结构、过期与淘汰。

  2. 持久化:RDB 快照 + AOF 日志 + 混合持久化。

  3. 高可用:主从复制、哨兵、Cluster 哈希槽分片。

  4. 工程难题:缓存穿透/击穿/雪崩、缓存一致性、分布式锁、RedLock 争议。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 1:45:32

大模型技术全景(二十):RAG 文本分块策略与语义完整性

📚 本文收录于「流浪」的系列专栏 🐧 Linux系统⚙️ C📊 数据结构与算法🐍 Python🔗 LangChain & LangGraph🗄️ MySQL 数据库🌿 Git 工具🌐 计算机网络🤖 LLM&…

作者头像 李华
网站建设 2026/10/11 1:44:52

GitHub日榜趋势速报系统设计与工程实践

1. 项目概述:这不是一份普通榜单,而是一张实时技术风向标“GitHub 日榜趋势速报 | 2026-10-02”——看到这个标题,第一反应不是点开看热闹,而是立刻调出终端、打开浏览器开发者工具、顺手记下三个关键动作:确认数据源可…

作者头像 李华
网站建设 2026/10/11 1:44:43

AI生成代码敢直接上线吗? 从测试到安全扫描,搭建6道自动化质量门禁

AI生成代码敢直接上线吗? 从测试到安全扫描,搭建6道自动化质量门禁 图 1 AI生成代码上线前的六道自动化质量门禁 人工智能 软件测试 自动化测试 CI/CD DevOps DevSecOps GitHub Actions pytest 代码质量 性能测试 AI生成代码把“写出来”的速度提升了,但真正决定代…

作者头像 李华