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

EEva·二月 27, 2026·7 min read

一、 常规问题

什么是 Redis,为什么要使用它?
Redis 是一个开源的,基于内存运行的键值型(Key-Value)NoSQL 数据库。它以极高的读写速度著称,常被用作缓存、数据库或消息中间件。
使用 Redis 的原因主要如下:
* 高3截大量请求:保护后端数据库不被大流量冲垮;
* 多数据结构:支持 String、Hash、List、Set、ZSet 等,能直接在内存中处理复杂的业务逻辑;
* 原子性:所有操作均为原子性,天然适合处理计数器、分布式锁等并发场景。

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

Redis 为什么快?
* 基于内存操作:数据直接存储在内存中,省去了磁盘 I/O 的寻道与读写开销(内存访问速度比磁盘快数万倍)。
* 单线程模型:核心网络处理采用单线程,避免了多线程环境下的上下文切换和锁竞争开销,保证了操作的原子性。
* IO 的多路复用:使用 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 数据类型有哪些命令?
(注:此处直接引出后续问题)

3. 谈谈 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 数据类型有哪些底层数据结构?
常用类型底层数据结构:
* String:SDS (简单动态字符串)
* List:quicklist (双向链表 + ziplist/listpack 的结合体)
* Hash:ziplist (压缩列表) 或 hashtable (哈希表)
* Set:intset (整数集合) 或 hashtable
* ZSet:ziplist 或 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 (日志) 记录每一个写命令,以追加方式保存。 数据更安全(秒级丢失)、日志可读性强。 文件大、恢复慢、高并发下有 IO 瓶颈。
  • 一般用法:混合持久化(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(往返时延)。
* 效果:极大提升批量操作的性能。从“发一个收一个”变成“发一堆收一堆”。