Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Redis — 用问答的方式拆解分布式缓存的核心(上)


常规问题

什么是 Redis,为什么要使用它?

Redis 是一个开源的、基于内存运行的键值型(Key-Value)NoSQL 数据库。它以极高的读写速度著称,常被用作缓存、数据库或消息中间件。使用 Redis 的原因有以下几点:

  • 高性能:拦截大量请求,保护后端数据库不被大流量冲垮;
  • 多数据结构:支持 String、Hash、List、Set、ZSet 等,能直接在内存中处理复杂的业务逻辑;
  • 原子性:所有操作均为原子性,天然适合处理计数器、分布式锁等并发场景。

Redis 一般有哪些使用场景?

  • 缓存:存储热点数据(如商品、用户信息等),大幅降低数据库压力,提升响应速度。
  • 分布式锁:利用 SETNX 等原子操作,解决分布式系统下的资源竞争问题。
  • 计数器 / 限流:实现点赞数、播放量统计,或通过计数器限制 API 的访问频率。
  • 排行榜:利用 ZSet 自动排序功能,实现实时积分或热度榜单。
  • 会话管理:在分布式集群中统一存储用户的登录状态,实现多机共享。
  • 消息队列:利用 List 或 Stream 结构,实现简单的异步任务处理和解耦。

Redis 为什么快?

  • 基于内存操作:数据直接存储在内存中,省去了磁盘 I/O 的寻道与读写开销(内存访问速度比磁盘快数万倍)。
  • 单线程模型:核心网络处理采用单线程,避免了多线程环境下的上下文切换和锁竞争开销,保证了操作的原子性。
  • I/O 多路复用:使用 epoll 非阻塞 I/O 模型,单个线程即可高效处理数万个并发连接。
  • 高效的数据结构:Redis 内部对各种结构(如 SDS 字符串、跳表 SkipList、压缩列表 ZipList)进行了极致的内存优化和算法优化。

数据类型和数据结构

Redis 有哪些数据类型?

五种基础数据类型

类型说明应用场景
String最基础类型,二进制安全,最大 512MB缓存、计数器、分布式锁、验证码
Hash键值对集合(如 user:101 {name: "Tom", age: 18}存储对象、购物车
List简单的字符串列表,按插入顺序排序消息队列、最新动态、时间轴
Set无序且不重复的字符串集合标签、共同好友、抽奖去重
ZSet有序集合,每个元素关联一个 double 类型的分数,按分数排序排行榜、热搜、延时队列

三种高级数据类型

类型说明应用场景
Bitmap基于 String,通过位操作记录 0/1 状态,极省空间用户签到、活跃状态统计
HyperLogLog概率型数据结构,统计基数,大数据量下仅占约 12KB 内存,误差约 0.81%亿级 UV 统计
GeoSpatial (GEO)存储经纬度信息,用于计算附近的人或两点之间的距离附近的人、打车距离计算

新一代数据类型

  • Stream:Redis 5.0 新增,主要用于实现持久化的消息队列(类似 Kafka),解决了 List 做队列时消息丢失的问题。

谈谈 Redis 的对象机制(redisObject)

typedef struct redisObject {
    unsigned type:4;       // 1. 类型(对外,即通常说的 5 大数据类型)
    unsigned encoding:4;   // 2. 编码(对内,内部编码)
    unsigned lru:24;       // 3. 记录 LRU/LFU 信息(用于淘汰)
    int refcount;          // 4. 引用计数(用于内存回收)
    void *ptr;             // 5. 指针(指向底层实际的数据结构)
} robj;

设计这套对象机制的原因有以下三点:

  • 解耦:命令(如 LLEN)只需要针对 List 类型,不需要关心底层是 ZipList 还是 LinkedList。
  • 极致的内存优化:小数据量用紧凑存储(时间换空间),大数据量用高效索引(空间换时间)。
  • 智能维护:自带引用计数和访问记录,自动处理内存回收和缓存淘汰。

Redis 数据类型有哪些底层数据结构?

常用类型底层数据结构
StringSDS(简单动态字符串)
Listquicklist(双向链表 + ziplist/listpack 的结合体)
Hashziplist(压缩列表)或 hashtable(哈希表)
Setintset(整数集合)或 hashtable
ZSetziplist 或 skiplist + hashtable

为什么要设计 SDS?

Redis 没有直接使用 C 语言的字符串(char*),而是自己封装了 SDS。C 语言原生的字符串(以 \0 结尾)无法满足 Redis 对高性能和安全性的要求。SDS 的设计优势如下:

  • 常数复杂度获取长度:内部记录了 len 属性,读取长度的时间复杂度为 O(1)。
  • 杜绝缓冲区溢出:修改前会先检查空间是否足够,不足则自动扩容。
  • 减少内存重分配:采用空间预分配和惰性空间释放策略。
  • 二进制安全:不以 \0 判断结束,可以存储图片、音频等二进制数据。

一个字符串类型的值能存储的最大容量是多少?

512 MB


为什么会设计 Stream?

在 Stream 出现之前,Redis 的消息发布订阅有明显痛点:

  • List 类型:虽能持久化,但不支持多消费组,确认机制(ACK)实现复杂。
  • Pub/Sub:无法持久化,消息“发完即丢“,消费者离线会导致消息丢失。

Stream 的设计目标:提供一个支持持久化、支持多消费组、支持消息确认机制的高可用消息队列模型。


Stream 用在什么场景?

  • 异步任务处理:需要保证消息不丢失的任务流。
  • 多端消费:同一个数据流需要被不同的业务系统(如结算系统、通知系统)同时消费。
  • 高性能日志采集:利用其追加写入的特性,记录海量流水数据。

消息 ID 的设计是否考虑了时间回拨的问题?

考虑了。 Stream 的 ID 默认格式是 <millisecondsTime>-<sequenceNumber>

  • 防御机制:Redis 会记录服务器当前最大的 ID 时间戳。
  • 处理逻辑:如果系统时间发生回拨,导致产生的时间戳小于上一个 ID,Redis 会强制使用上一次的时间戳,并递增其序列号,从而保证 ID 的单调递增性。

持久化和内存

Redis 的持久化机制是什么?各自的优缺点?一般怎么用?

机制原理优点缺点
RDB(快照)定期将内存数据生成二进制文件保存恢复快、文件体积小、性能开销低数据丢失多(最后一次快照后会丢)、生成快照耗时
AOF(日志)记录每一个写命令,以追加方式保存数据更安全(秒级丢失)、日志可读性强文件大、恢复慢、高并发下有 I/O 瓶颈

一般用法:混合持久化(RDB + AOF)。用 RDB 做全量备份,用 AOF 做增量记录,兼顾安全与速度。


Redis 过期键的删除策略有哪些?

Redis 采用 “惰性删除 + 定期删除” 配合使用:

  • 惰性删除:访问 key 时才检查是否过期,过期则删除。(省 CPU,费内存)
  • 定期删除:每隔一段时间随机抽取一部分 key 检查并删除。(折中方案)

Redis 内存淘汰算法有哪些?

当内存达到 maxmemory 限制时,触发以下算法:

算法说明
LRU(Least Recently Used)淘汰最久没被访问的数据
LFU(Least Frequently Used)淘汰访问频率最低的数据
Random随机淘汰
TTL优先淘汰快过期的数据
Noeviction不淘汰,写操作直接报错(默认配置)

Redis 的内存用完了会发生什么?

  • 如果设置了淘汰策略(如 allkeys-lru),Redis 会根据算法自动删除旧数据腾出空间。
  • 如果没有设置策略或策略为 noeviction,Redis 将拒绝所有写请求(报错 OOM),但读请求正常。

Redis 如何做内存优化?

  • 控制 Key 长度:缩短键名。
  • 避免存储大 Key:拆分大的 Hash 或 List。
  • 使用高效编码:尽量利用 ZipList(压缩列表)存储小规模数据。
  • 设置过期时间:确保冷数据能自动释放。
  • 开启内存碎片整理:配置 activedefrag yes

Redis key 的过期时间和永久有效分别怎么设置?

  • 设置过期EXPIRE key secondsPEXPIRE key milliseconds
  • 永久有效:默认创建即永久。若需取消过期时间,使用 PERSIST key

Redis 中的管道有什么用?

  • 作用:将多个命令打包一次性发送给服务器,减少网络 RTT(往返时延)。
  • 效果:极大提升批量操作的性能。从“发一个收一个“变成“发一堆收一堆“。