Skip to content

Redis 核心

概念

Redis(Remote Dictionary Server)是一个开源的、基于内存的键值数据库,支持多种数据结构,常用于缓存、消息队列、分布式锁等场景。

核心特点:

  • 纯内存操作:数据存储在内存中,读写速度极快(读约 10 万 QPS,写约 8 万 QPS)
  • 单线程命令执行:避免竞态条件,无需加锁
  • 丰富的数据结构:String、List、Hash、Set、Sorted Set 等
  • 持久化支持:RDB 快照 + AOF 日志
  • 高可用:主从复制、哨兵、集群

核心原理

1. 五种基本数据类型与底层数据结构

类型底层结构适用场景
StringSDS(简单动态字符串)缓存对象、计数器、分布式锁
Listquicklist(ziplist + 双向链表)消息队列、最新列表、分页
Hashziplist(小数据)/ hashtable(大数据)存储对象字段、用户信息
Setintset(纯整数)/ hashtable(其他)去重、交集并集差集、标签系统
Sorted Setziplist(小数据)/ skiplist + hashtable(大数据)排行榜、带权重队列、范围查询

各结构详解:

String — SDS(Simple Dynamic String)

struct sdshdr {
    int len;    // 已使用长度
    int free;   // 剩余空间
    char buf[]; // 字节数组
};
  • 相比 C 字符串,SDS 记录长度,strlen 复杂度为 O(1)
  • 预分配空间,减少内存重分配次数
  • 二进制安全,可存储任意二进制数据

List — quicklist

  • Redis 3.2 之后,List 统一用 quicklist 实现
  • quicklist = 多个 ziplist 组成的双向链表
  • ziplist 节点数量由 list-max-ziplist-size 控制,平衡内存与性能

Hash — ziplist / hashtable

  • 元素数量 ≤ hash-max-ziplist-entries(默认 128)且值长度 ≤ hash-max-ziplist-value(默认 64 字节)时使用 ziplist
  • 超出阈值自动转为 hashtable

Set — intset / hashtable

  • 全为整数且数量 ≤ set-max-intset-entries(默认 512)时使用 intset
  • intset 有序存储,查找用二分,内存紧凑

Sorted Set — ziplist / skiplist + hashtable

  • 元素数量 ≤ zset-max-ziplist-entries(默认 128)时使用 ziplist
  • 超出阈值转为 skiplist(跳表)+ hashtable 的组合
    • skiplist 支持范围查询(ZRANGEZRANGEBYSCORE
    • hashtable 支持 O(1) 单点查找(ZSCORE

2. 持久化机制

RDB(Redis Database Snapshot)

RDB 是将某一时刻内存中的数据以二进制快照的形式写入磁盘。

触发方式:

  • save:阻塞主进程,同步写入(生产环境不推荐)
  • bgsave:fork 一个子进程异步写入,主进程继续处理请求
  • 配置自动触发:save 900 1(900 秒内有 1 次写操作则触发)

bgsave 流程:

主进程 --fork--> 子进程
                  |
                  | 遍历内存数据,写入临时 RDB 文件
                  |
                  v
              替换旧 RDB 文件
  • fork 使用 Copy-On-Write(COW) 机制,fork 时不复制内存,只有写操作时才复制对应页面
  • 优点:文件紧凑,恢复速度快,适合灾难恢复和备份
  • 缺点:两次快照之间的数据可能丢失

AOF(Append Only File)

AOF 将每条写命令以文本形式追加到日志文件,重启时重放命令恢复数据。

三种 fsync 策略:

策略行为性能数据安全性
always每条命令写入后立即 fsync最慢最高,最多丢失 1 条命令
everysec(默认)每秒执行一次 fsync适中较高,最多丢失 1 秒数据
no由操作系统决定 fsync 时机最快最低,可能丢失较多数据

AOF 重写(Rewrite):

  • AOF 文件会随时间增大,通过 bgrewriteaof 命令压缩
  • 子进程基于当前内存状态重写,期间新写命令同时追加到缓冲区
  • 重写完成后将缓冲区内容追加到新 AOF 文件,然后替换旧文件

混合持久化(Redis 4.0+)

[ RDB 格式的全量数据 ][ AOF 格式的增量命令 ]
        RDB 头                  AOF 尾
  • 配置:aof-use-rdb-preamble yes
  • 重启时先加载 RDB 部分(速度快),再重放 AOF 部分(数据完整)
  • 兼顾恢复速度与数据安全性

RDB vs AOF 对比

对比维度RDBAOF
文件大小小(二进制压缩)大(文本命令)
恢复速度慢(需重放命令)
数据完整性低(可能丢失几分钟数据)高(最多丢失 1 秒)
写性能影响低(异步 fork)低~中(取决于 fsync 策略)
适用场景备份、容灾数据安全要求高的场景

如何选择:

  • 对数据安全要求高 → AOF(everysec
  • 对恢复速度要求高 → RDB
  • 生产推荐 → 混合持久化(两者兼顾)

3. 内存淘汰策略(8 种)

当内存达到 maxmemory 上限时,Redis 根据配置的策略淘汰键。

策略淘汰范围淘汰算法说明
noeviction不淘汰,写操作直接报错(默认)
allkeys-lru所有键LRU淘汰最近最少使用的键
volatile-lru设有过期时间的键LRU对有 TTL 的键做 LRU 淘汰
allkeys-random所有键随机随机淘汰任意键
volatile-random设有过期时间的键随机随机淘汰有 TTL 的键
volatile-ttl设有过期时间的键TTL优先淘汰剩余 TTL 最短的键
allkeys-lfu所有键LFU淘汰使用频率最低的键(4.0+)
volatile-lfu设有过期时间的键LFU对有 TTL 的键做 LFU 淘汰(4.0+)

LRU vs LFU:

维度LRU(最近最少使用)LFU(最不常使用)
核心思路按最近访问时间排序按访问频率排序
优点实现简单,适合访问时间局部性强的场景能识别"冷门但最近访问一次"的键
缺点偶发访问的冷数据可能挤占热数据新键初始频率低,可能被过早淘汰
适用场景通用缓存热点数据分布稳定的场景

Redis 的 LRU/LFU 是近似实现,通过采样(默认 5 个键)选出最优淘汰对象,而非严格维护全局排序,以节省内存。


4. 缓存三大问题

缓存穿透(Cache Penetration)

问题: 查询一个数据库中也不存在的数据,缓存永远没有,每次请求都打到数据库。

请求 id=-1
  → 缓存未命中
  → 查数据库 → 无结果
  → 不写缓存
  → 下次同样请求继续穿透

恶意攻击场景:大量不存在的 key 请求,导致数据库压力激增。

解决方案:

方案原理优点缺点
缓存空值查到空结果也写入缓存,设置短 TTL简单易实现浪费缓存空间,不适合大量不同 key
布隆过滤器预先将合法 key 加入过滤器,请求先过滤内存占用小,拦截效果好有误判率,不支持删除(Counting BF 可以)

缓存击穿(Cache Breakdown)

问题: 某个热点 key 恰好过期的瞬间,大量并发请求同时穿透到数据库。

热点 key 过期
  → 大量并发请求同时缓存未命中
  → 同时查数据库 → 数据库压力激增

解决方案:

方案原理优点缺点
互斥锁(分布式锁)只允许一个请求查数据库并回写缓存,其他请求等待数据强一致等待期间有延迟,锁超时处理复杂
逻辑过期key 永不设 TTL,在 value 中存逻辑过期时间,过期时异步更新无等待,性能好可能短暂返回旧数据(最终一致)

缓存雪崩(Cache Avalanche)

问题: 大量 key 在同一时刻过期(或 Redis 服务宕机),请求全部打到数据库,导致数据库崩溃。

解决方案:

方案说明
随机过期时间在基础 TTL 上加随机偏移,避免集中过期
热点 key 永不过期逻辑过期 + 后台异步刷新
Redis 集群高可用哨兵或 Cluster 模式,避免单点故障导致雪崩
服务降级 + 限流数据库层面做熔断,保护数据库
多级缓存本地缓存(Caffeine)+ Redis,Redis 宕机时本地缓存兜底

5. 单线程模型与 I/O 多路复用

为什么单线程还快?

Redis 命令执行是单线程的,但性能极高,原因如下:

原因说明
纯内存操作内存访问速度远快于磁盘,无 I/O 等待
非阻塞 I/O使用 epoll/kqueue 等 I/O 多路复用,单线程处理大量连接
避免上下文切换无多线程切换开销,无锁竞争
简单高效的数据结构底层数据结构针对内存操作优化

I/O 多路复用模型(以 epoll 为例):

多个客户端连接

   epoll 监听

  就绪事件队列

  单线程依次处理
  (读取命令 → 执行 → 返回响应)
  • 单线程处理命令,不存在竞态条件,无需加锁
  • 瓶颈在网络 I/O,而非 CPU

Redis 6.0 多线程 I/O

版本线程模型
Redis 6.0 之前网络 I/O + 命令执行 均为单线程
Redis 6.0+网络 I/O(读写)多线程 + 命令执行仍为单线程
  • 多线程只负责网络数据的读取和写回,提升网络吞吐量
  • 命令的实际执行依然是单线程串行,保证原子性和线程安全
  • 默认关闭,需手动开启:io-threads 4

6. 集群方案

主从复制(Master-Replica Replication)

全量同步(初次同步):

Replica 发送 PSYNC replicationid offset
Master 执行 bgsave 生成 RDB → 发送给 Replica
Replica 加载 RDB
Master 将 RDB 生成期间的写命令发送给 Replica(replication buffer)

增量同步(断线重连):

  • Replica 重连后,发送上次同步的 offset
  • Master 从 repl_backlog_buffer 中找到缺失的命令发送
  • 若 offset 已不在 backlog 中(缓冲区满),则触发全量同步

特点:

  • 读写分离:Master 负责写,Replica 负责读
  • 无自动故障转移,Master 宕机需手动切换

哨兵模式(Sentinel)

哨兵是独立进程,监控 Redis 节点健康状态,实现自动故障转移。

Sentinel 集群(建议 3 个节点)
    ↓ 监控
Master + Replica(s)

核心功能:

功能说明
监控(Monitoring)定期向 Master/Replica 发送 PING,检测是否存活
通知(Notification)故障时通知客户端或管理员
自动故障转移(Failover)Master 宕机时,从 Replica 中选出新 Master
配置中心客户端通过 Sentinel 获取当前 Master 地址

主观下线 vs 客观下线:

  • 主观下线(SDOWN):单个 Sentinel 认为节点不可达
  • 客观下线(ODOWN):超过 quorum 数量的 Sentinel 都认为节点不可达,才触发故障转移

Redis Cluster(分片集群)

Redis Cluster 通过数据分片实现水平扩展,支持 PB 级数据。

核心概念:

概念说明
Slot(槽)16384 个 slot,每个 key 通过 CRC16(key) % 16384 映射到 slot
节点分配每个 Master 节点负责一部分 slot,各 Master 有对应 Replica
Gossip 协议节点间通过 Gossip 协议交换状态信息,去中心化
重定向客户端请求错误节点时收到 MOVED/ASK 响应,重定向到正确节点

为什么是 16384 个 slot?

  • 16384 = 2^14,心跳包中用 bitmap 表示 slot 分配,16384 个 slot 只需 2KB
  • 节点数通常不超过 1000,16384 个 slot 粒度足够,不需要更多

三种集群方案对比:

方案高可用水平扩展自动故障转移复杂度
主从复制中(手动切换)
哨兵模式否(单 Master 写)
Redis Cluster

面试常问 & 怎么答

Q1: Redis 为什么这么快?

答题思路: 从存储、I/O 模型、线程模型三个维度展开。

参考回答:

Redis 快的原因主要有以下几点:

  1. 纯内存操作:所有数据存储在内存中,读写不涉及磁盘 I/O,内存访问延迟在纳秒级别
  2. 高效的数据结构:底层使用 SDS、skiplist、ziplist 等针对内存优化的数据结构,操作复杂度低
  3. I/O 多路复用:通过 epoll 等机制,单线程可以同时监听大量连接,非阻塞处理网络事件
  4. 单线程命令执行:避免了多线程的上下文切换开销和锁竞争,逻辑简单,执行效率高
  5. Redis 6.0 网络 I/O 多线程:网络读写阶段引入多线程,进一步提升网络吞吐量,命令执行仍为单线程保证原子性

补充:Redis 的性能瓶颈通常在网络带宽,而非 CPU 或内存。


Q2: 缓存穿透、击穿、雪崩分别是什么?怎么解决?

答题思路: 三者都是缓存失效导致请求打到数据库,但原因和场景不同,解决方案也不同。

问题触发场景核心解决方案
缓存穿透查询数据库中不存在的数据布隆过滤器拦截非法 key;或缓存空值
缓存击穿单个热点 key 突然过期,大量并发打入互斥锁(强一致);逻辑过期(高性能)
缓存雪崩大量 key 同时过期,或 Redis 宕机TTL 加随机偏移;集群高可用;多级缓存兜底

参考回答要点:

  • 穿透强调"数据压根不存在",用布隆过滤器在请求入口拦截
  • 击穿强调"一个热点 key 的过期瞬间",用互斥锁保证只有一个请求重建缓存
  • 雪崩强调"大批量 key 同时失效",用随机 TTL 打散过期时间,用集群避免单点故障

Q3: RDB 和 AOF 有什么区别?怎么选?

答题思路: 从文件格式、数据安全、恢复速度、性能影响四个维度对比,再给出选择建议。

参考回答:

维度RDBAOF
文件格式二进制快照,文件小文本命令日志,文件大
数据安全低,两次快照间数据可能丢失高,最多丢失 1 秒(everysec)
恢复速度快(直接加载快照)慢(需重放所有命令)
写性能低影响(fork 子进程异步)中等影响(everysec 策略)

选择建议:

  • 对数据安全要求高(金融、支付)→ AOF(everysec
  • 对启动恢复速度要求高,允许少量数据丢失 → RDB
  • 生产环境推荐混合持久化aof-use-rdb-preamble yes),兼顾恢复速度和数据安全

Q4: Redis 集群的三种方案分别是什么?

答题思路: 按演进顺序介绍,每种方案说清楚解决了什么问题、有什么局限。

参考回答:

  1. 主从复制:基础方案,Master 写、Replica 读,实现读写分离和数据备份。缺点是 Master 宕机需手动切换,不支持自动故障转移。

  2. 哨兵模式(Sentinel):在主从基础上引入哨兵进程,实现自动故障检测和故障转移。多个哨兵通过投票(quorum)决策,防止误判。缺点是写操作仍集中在单个 Master,无法水平扩展写能力。

  3. Redis Cluster:将数据分片到 16384 个 slot,分布在多个 Master 节点上,实现水平扩展。每个 Master 有对应 Replica,节点间用 Gossip 协议同步状态,支持自动故障转移。缺点是架构复杂,跨 slot 的多 key 操作受限。

如何选择:

  • 数据量小、并发不高 → 主从复制
  • 需要高可用但数据量可控 → 哨兵模式
  • 数据量大、需要水平扩展写能力 → Redis Cluster

看到什么就先想到这类

关键词 / 场景首先想到
排行榜、积分榜Sorted Set(ZADD / ZRANGE
去重、共同好友Set(交集 SINTER、并集 SUNION
用户会话、Token 缓存String + TTL
购物车、用户信息Hash
消息队列、最新动态List(LPUSH / RPOP
分布式锁String + SET key value NX EX(或 Redisson)
防止缓存穿透布隆过滤器
热点 key 过期保护逻辑过期 + 互斥锁
大量 key 同时过期TTL 随机偏移 + 多级缓存
数据安全要求高的持久化AOF everysec + 混合持久化
Redis 自动故障切换哨兵(Sentinel)
海量数据 + 高并发写Redis Cluster
计数器(点赞数、访问量)String INCR / INCRBY
限流String INCR + TTL,或 Sorted Set 滑动窗口