All articles

Redis常见面试题总结

覆盖 Redis 数据结构、持久化、缓存策略、集群与高可用机制的常见面试知识。

2025-10-26 · Updated 2025-11-19 · 14 分钟阅读

Redis 基础

什么是 Redis?

Redis (REmote DIctionary Server)是一个基于 C 语言开发的开源 NoSQL 数据库(BSD 许可)。与传统数据库不同的是,Redis 的数据是保存在内存中的(内存数据库,支持持久化),因此读写速度非常快,被广泛应用于分布式缓存方向。并且,Redis 存储的是 KV 键值对数据。

为了满足不同的业务场景,Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO)。并且,Redis 还支持事务、持久化、Lua 脚本、发布订阅模型、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster)。

Redis 没有外部依赖,Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。

Redis 为什么这么快?

Redis 内部做了非常多的性能优化,比较重要的有下面 4 点:

  1. 纯内存操作 (Memory-Based Storage) :这是最主要的原因。Redis 数据读写操作都发生在内存中,访问速度是纳秒级别,而传统数据库频繁读写磁盘的速度是毫秒级别,两者相差数个数量级。
  2. 高效的 I/O 模型 (I/O Multiplexing & Single-Threaded Event Loop) :Redis 使用单线程事件循环配合 I/O 多路复用技术,让单个线程可以同时处理多个网络连接上的 I/O 事件(如读写),避免了多线程模型中的上下文切换和锁竞争问题。虽然是单线程,但结合内存操作的高效性和 I/O 多路复用,使得 Redis 能轻松处理大量并发请求。
  3. 优化的内部数据结构 (Optimized Data Structures) :Redis 提供多种数据类型(如 String, List, Hash, Set, Sorted Set 等),其内部实现采用高度优化的编码方式(如 ziplist, quicklist, skiplist, hashtable 等)。Redis 会根据数据大小和类型动态选择最合适的内部编码,以在性能和空间效率之间取得最佳平衡。
  4. 简洁高效的通信协议 (Simple Protocol - RESP) :Redis 使用的是自己设计的 RESP (REdis Serialization Protocol) 协议。这个协议实现简单、解析性能好,并且是二进制安全的。客户端和服务端之间通信的序列化/反序列化开销很小,有助于提升整体的交互速度。

参考:Why is Redis so fast?

那既然都这么快了,为什么不直接用 Redis 当主数据库呢?主要是因为内存成本太高,并且 Redis 提供的数据持久化仍然有数据丢失的风险。

Redis 是单线程还是多线程?

Redis 在 6.0 版本之前是单线程的,从 6.0 版本开始引入了多线程。

Redis 6.0 之前为什么是单线程?

Redis 使用单线程的主要原因:

  1. 避免上下文切换开销:多线程环境下,线程间的上下文切换会带来 CPU 开销,影响性能
  2. 避免锁竞争:多线程需要加锁来保证数据一致性,锁竞争会降低性能
  3. 内存操作本身很快:Redis 的数据操作都在内存中完成,CPU 不是瓶颈,I/O 才是
  4. I/O 多路复用:使用 epoll、select 等 I/O 多路复用技术,单线程也能高效处理大量并发连接

Redis 6.0 为什么引入多线程?

Redis 6.0 引入多线程主要是为了提升网络 I/O 的读写性能,而不是为了处理命令执行。

  • 主线程:仍然负责命令的执行
  • I/O 线程:负责网络 I/O 的读写(解析请求、发送响应)

这样的设计既保持了命令执行的原子性(单线程执行),又提升了网络 I/O 的性能。

可以通过 io-threads 配置项开启多线程:

io-threads 4  # 开启 4 个 I/O 线程

除了 Redis,你还知道其他分布式缓存方案吗?

分布式缓存的话,比较老牌同时也是使用的比较多的还是 MemcachedRedis。不过,现在基本没有看过还有项目使用 Memcached 来做缓存,都是直接用 Redis

Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。

有一些大厂也开源了类似于 Redis 的分布式高性能 KV 存储数据库,例如,腾讯开源的 Tendis。Tendis 基于知名开源项目 RocksDB 作为存储引擎 ,100% 兼容 Redis 协议和 Redis4.0 所有数据模型。

Redis 数据类型

Redis 有哪些数据类型?

Redis 提供了丰富的数据类型,常见的有:

  1. String(字符串):最基本的数据类型,可以存储字符串、整数或浮点数
  2. Hash(哈希):键值对集合,适合存储对象
  3. List(列表):字符串列表,按照插入顺序排序
  4. Set(集合):无序的字符串集合,不允许重复
  5. Sorted Set(有序集合):带分数的字符串集合,按分数排序
  6. Bitmap(位图):字符串的二进制操作,适合做统计
  7. HyperLogLog:用于基数统计(去重统计)
  8. GEO(地理位置):存储地理位置信息

Redis 内部数据结构

Redis 内部使用了多种数据结构来优化不同数据类型的存储:

  • **SDS (Simple Dynamic String)**:用于字符串存储
  • ziplist(压缩列表):用于小数据量的 List、Hash、Sorted Set
  • quicklist:List 的内部实现,是 ziplist 和双向链表的结合
  • skiplist(跳跃表):Sorted Set 的内部实现
  • dict(哈希表):Hash 和 Set 的内部实现
  • intset(整数集合):小整数 Set 的优化实现

Redis 持久化

Redis 持久化机制有哪些?

Redis 提供了两种持久化机制:

  1. RDB(Redis Database):快照持久化
  2. AOF(Append Only File):追加式持久化

RDB 持久化

RDB 是 Redis 的默认持久化方式,它会将某个时间点的数据快照保存到磁盘。

优点:

  • 文件紧凑,适合做备份
  • 恢复速度快
  • 对 Redis 性能影响小

缺点:

  • 可能会丢失最后一次快照后的数据
  • 数据量大时,fork 子进程可能会阻塞主进程

触发方式:

  • 手动执行 SAVEBGSAVE 命令
  • 根据配置文件的规则自动触发
  • 主从复制时,从节点接收到 RDB 文件

配置示例:

save 900 1      # 900秒内至少1个key发生变化
save 300 10     # 300秒内至少10个key发生变化
save 60 10000   # 60秒内至少10000个key发生变化

AOF 持久化

AOF 持久化通过记录每个写命令来持久化数据,类似于 MySQL 的 binlog。

优点:

  • 数据更安全,最多丢失1秒数据(fsync everysec)
  • 可读性好,便于分析

缺点:

  • 文件体积大
  • 恢复速度较慢
  • AOF 重写时会占用较多资源

AOF 同步策略:

  • always:每次写入都同步,最安全但性能最低
  • everysec:每秒同步一次,平衡安全性和性能(推荐)
  • no:由操作系统决定何时同步,性能最好但可能丢失较多数据

配置示例:

appendonly yes
appendfsync everysec

RDB 和 AOF 如何选择?

  • 生产环境建议两者都开启,RDB 用于备份,AOF 用于数据恢复
  • 如果只选一个,优先选择 AOF(数据安全更重要)
  • 混合持久化(Redis 4.0+):RDB 和 AOF 混合使用,兼顾速度和安全性

Redis 过期键删除策略

Redis 过期键的删除策略

Redis 采用了惰性删除 + 定期删除的组合策略:

  1. 惰性删除(Lazy Expiration):当访问一个 key 时,检查它是否过期,如果过期就删除。这种方式不会主动扫描,对 CPU 友好,但可能导致过期 key 长期占用内存。

  2. 定期删除(Periodic Expiration):Redis 会每隔一段时间(默认 100ms)随机抽取一些设置了过期时间的 key,检查并删除过期的 key。通过限制扫描的时长和频率,减少对 CPU 的影响。

大量 key 过期会导致什么问题?

如果过期 key 没有及时清理,可能会导致:

  • 内存占用过高,甚至引发内存溢出(OOM)
  • 过期 key 已经失效,但在 Redis 真正删除它们之前,仍然会占用内存空间

解决方案:

  1. 避免 key 集中过期:在设置键的过期时间时尽量随机一点
  2. 开启 lazy free 机制:修改 redis.conf,将 lazyfree-lazy-expire 设置为 yes,Redis 会在后台异步删除过期的 key,不会阻塞主线程

Redis 内存淘汰策略

Redis 内存淘汰策略有哪些?

当 Redis 运行内存达到了配置的最大内存阈值时(maxmemory 参数),会触发内存淘汰。Redis 提供了 8 种内存淘汰策略:

  1. volatile-lru:从已设置过期时间的数据集中移除最近最少使用的数据
  2. volatile-lfu(4.0+):从已设置过期时间的数据集中移除最不经常使用的数据
  3. volatile-random:从已设置过期时间的数据集中随机移除数据
  4. volatile-ttl:从已设置过期时间的数据集中移除将要过期的数据
  5. allkeys-lru:从所有数据集中移除最近最少使用的数据(推荐
  6. allkeys-lfu(4.0+):从所有数据集中移除最不经常使用的数据
  7. allkeys-random:从所有数据集中随机移除数据
  8. no-eviction(默认):禁止驱逐数据,当内存不足时,新写入操作会报错

allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期时间的键值中淘汰数据。

配置示例:

maxmemory 2gb
maxmemory-policy allkeys-lru

Redis 缓存问题

缓存穿透

问题描述: 大量请求查询数据库中不存在的数据,导致请求直接打到数据库上。

解决方案:

  1. 参数校验:对请求参数进行校验,过滤掉明显不合法的请求
  2. 缓存空值:如果查询结果为空,将空结果也缓存起来,并设置较短的过期时间
  3. 布隆过滤器:在缓存之前加一层布隆过滤器,判断数据是否存在,不存在直接返回

缓存击穿

问题描述: 热点数据过期,大量请求同时查询数据库,导致数据库压力骤增。

解决方案:

  1. 热点数据永不过期:设置热点数据不过期,或者在异步线程中更新
  2. 互斥锁:使用分布式锁,只允许一个线程查询数据库并更新缓存
  3. 多级缓存:本地缓存 + Redis 缓存

缓存雪崩

问题描述: 大量缓存同时过期,或者 Redis 服务宕机,导致大量请求直接打到数据库。

解决方案:

  1. 过期时间随机化:在设置过期时间时,加上随机值,避免大量 key 同时过期
  2. 热点数据永不过期:关键数据设置永不过期,或者定期异步更新
  3. 多级缓存:本地缓存 + Redis 缓存,提高可用性
  4. 限流降级:当大量请求打到数据库时,进行限流和降级处理
  5. Redis 高可用:使用 Redis 集群、主从复制等方案提高可用性

Redis 高可用方案

Redis 主从复制

工作原理:

  1. 从节点连接到主节点,发送 SYNC 命令
  2. 主节点执行 BGSAVE 生成 RDB 文件,并发送给从节点
  3. 主节点在生成 RDB 期间,将写命令记录到缓冲区
  4. RDB 文件发送完成后,主节点将缓冲区的命令发送给从节点
  5. 之后主节点的每个写命令都会异步发送给从节点

优点:

  • 读写分离,提高读性能
  • 数据备份,提高数据安全性

缺点:

  • 主节点故障需要手动切换
  • 主从延迟可能影响数据一致性

Redis 哨兵模式(Sentinel)

Redis Sentinel 是 Redis 的高可用解决方案,它可以监控主从节点的健康状态,并在主节点故障时自动进行故障转移。

功能:

  1. 监控:监控主从节点是否正常运行
  2. 通知:当被监控节点出现问题时,通知管理员
  3. 故障转移:主节点故障时,自动将从节点升级为主节点
  4. 配置提供者:客户端连接时,返回当前主节点地址

工作流程:

  1. 每个 Sentinel 节点都会向主节点、从节点和其他 Sentinel 节点发送 PING 命令
  2. 如果节点在 down-after-milliseconds 时间内没有回复,Sentinel 会标记为主观下线(SDOWN)
  3. 当足够多的 Sentinel 节点(超过配置的 quorum)都标记为主观下线时,标记为客观下线(ODOWN)
  4. 在 Sentinel 节点中选举出 Leader,由 Leader 进行故障转移
  5. 选择新的主节点,并通知其他从节点切换主节点

配置示例:

sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000

Redis 集群(Cluster)

Redis Cluster 是 Redis 的分布式解决方案,通过分片(Sharding)将数据分布到多个节点。

特点:

  • 无中心化:所有节点相互连接,节点之间使用 gossip 协议通信
  • 数据分片:使用哈希槽(Hash Slot)将数据分布到 16384 个槽中
  • 高可用:每个分片可以设置主从复制
  • 故障转移:节点故障时自动进行故障转移

哈希槽分配:

  • Redis 集群有 16384 个哈希槽(0-16383)
  • 每个节点负责一部分哈希槽
  • 客户端根据 key 计算 CRC16 值,然后对 16384 取模,得到哈希槽

优点:

  • 水平扩展,可以动态添加节点
  • 无单点故障
  • 自动故障转移

缺点:

  • 客户端需要支持集群模式
  • 批量操作可能涉及多个节点(需要使用 hash tag)
  • 事务和 Lua 脚本的 key 必须在同一个节点

Redis 事务

Redis 事务的特点

Redis 事务具有以下特点:

  1. 不保证原子性:Redis 事务中的命令如果出现错误,不会回滚已执行的命令
  2. 不支持回滚:Redis 不支持事务回滚(官方解释是为了性能考虑)
  3. 命令队列:事务中的命令会被放入队列,执行时一次性执行

事务命令:

  • MULTI:开启事务
  • EXEC:执行事务
  • DISCARD:取消事务
  • WATCH:监控 key,如果事务执行前 key 被修改,事务会失败

示例:

MULTI
SET key1 value1
SET key2 value2
EXEC

Redis 事务为什么不支持回滚?

官方解释是:

  1. Redis 命令失败通常是由于编程错误,应该在开发阶段发现并修复
  2. 回滚机制会增加复杂度,影响 Redis 的简洁性和性能
  3. Redis 追求简单和速度,而不是严格的 ACID 特性

Redis 分布式锁

如何实现 Redis 分布式锁?

基本实现:

SET key value NX EX seconds
  • NX:只有当 key 不存在时才设置
  • EX:设置过期时间

问题:

  1. 锁过期时间问题:如果业务执行时间超过锁的过期时间,锁会被其他进程获取
  2. 误删锁问题:进程 A 获取锁后,如果执行时间过长,锁过期后被进程 B 获取,进程 A 执行完后可能误删进程 B 的锁

解决方案:

  • 使用 Lua 脚本保证原子性
  • 设置锁的值(如 UUID),删除时验证值是否匹配
  • 使用 Redlock 算法(需要多个独立的 Redis 实例)

Lua 脚本实现:

-- 获取锁
if redis.call("get", KEYS[1]) == false then
    return redis.call("set", KEYS[1], ARGV[1], "EX", ARGV[2], "NX")
else
    return false
end

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

其他常见问题

Redis 和 Memcached 的区别

特性RedisMemcached
数据类型丰富(String、Hash、List、Set、Sorted Set 等)简单(仅 String)
持久化支持(RDB、AOF)不支持
集群模式原生支持(Redis Cluster)需要客户端实现
事务支持不支持
过期策略支持多种支持
单线程6.0 之前单线程,6.0+ 多线程 I/O多线程
内存管理支持多种淘汰策略LRU
适用场景缓存、消息队列、计数器等纯缓存

Redis 为什么使用跳跃表而不是平衡树?

Redis 的 Sorted Set 内部使用跳跃表(SkipList)而不是平衡树,主要原因:

  1. 实现简单:跳跃表的实现比平衡树简单
  2. 范围查询高效:跳跃表是链表结构,范围查询更高效
  3. 插入删除高效:跳跃表的插入删除操作更简单
  4. 内存占用:在某些场景下,跳跃表的内存占用更少

Redis 的 I/O 多路复用

Redis 使用 I/O 多路复用技术(如 epoll、select),使得单线程可以同时处理多个客户端连接。

原理:

  1. 将多个 I/O 操作注册到同一个线程的事件循环中
  2. 当某个 I/O 操作就绪时,操作系统会通知线程
  3. 线程处理就绪的 I/O 操作,然后继续等待下一个就绪事件

这样避免了为每个连接创建线程的开销,提高了并发性能。

总结

Redis 作为高性能的内存数据库,在分布式系统中扮演着重要角色。掌握 Redis 的核心概念、持久化机制、高可用方案和缓存问题的解决方案,对于系统设计和面试都非常重要。

在实际项目中,需要根据业务场景选择合适的持久化策略、内存淘汰策略和高可用方案,同时要注意缓存穿透、缓存击穿、缓存雪崩等问题的预防和处理。


参考资料:

Originally published on mlangTse's Blog. View source