Redis(Remote Dictionary Server,远程字典服务)是一个开源的、基于内存的高性能键值数据库,由 Salvatore Sanfilippo 使用 C 语言编写,既可以作为数据库、缓存,也可以作为消息中间件使用。
一、Redis 基础篇
1. 什么是 Redis?它有哪些特点?
Redis 的核心特点可以归纳为以下几点:
基于内存:数据存储在内存中,读写速度极快,单机 QPS 可以达到 10 万以上。
持久化:提供 RDB 和 AOF 两种持久化方式,可以把数据保存到磁盘,防止重启丢失。
数据结构丰富:不只是简单的 key-value,还支持 String、List、Hash、Set、ZSet、BitMap、HyperLogLog、GEO、Stream 等多种数据结构。
单线程模型:Redis 6.0 之前,命令执行是单线程的,避免了多线程的上下文切换和锁竞争问题。
功能丰富:支持发布订阅、事务、Lua 脚本、过期删除、内存淘汰、主从复制、哨兵、集群等。
支持高可用和分布式:通过 Sentinel 实现高可用,通过 Cluster 实现水平扩展。
2. Redis 为什么这么快?
主要原因包括:
纯内存操作:所有数据都保存在内存中,不涉及磁盘 IO,这是速度快的根本原因。
单线程避免切换开销:Redis 6.0 之前核心命令执行是单线程的,避免了上下文切换、锁竞争、CPU 缓存失效等问题。
高效的数据结构:例如 String 使用 SDS,Hash 使用压缩列表或哈希表存储。
IO 多路复用:使用 epoll/select/kqueue 等技术,一个线程可以同时监听多个客户端连接。
采用 Reactor 网络模型:网络部分采用事件驱动模型,把连接、读、写、关闭等都转化为事件来处理。
注意:Redis 6.0 引入了多线程,但多线程只用于「网络 IO 的读写」部分,命令的真正执行仍然是单线程的。
3. Redis 和 Memcached 有什么区别?
| 对比维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | String、List、Hash、Set、ZSet 等多种 | 仅支持简单的 key-value |
| 持久化 | 支持 RDB、AOF | 不支持,重启数据丢失 |
| 线程模型 | 单线程执行命令(6.0 后网络多线程) | 多线程 |
| 高可用 | 支持主从、哨兵、集群 | 不支持原生高可用,需要客户端实现 |
| 过期策略 | 惰性删除 + 定期删除 | 惰性删除 |
| 内存管理 | 可设置内存淘汰策略 | 内存分配采用 Slab,容易产生碎片 |
| value 大小 | 最大 512MB | 最大 1MB |
总结:需要复杂数据结构、持久化、高可用,选择 Redis;只是简单缓存且数据量极大、对数据类型没有要求,可以考虑 Memcached。大多数现代系统中,Redis 基本已成为默认选择。
4. Redis 的单线程模型是怎样的?为什么后来又引入多线程?
Redis 整体上采用基于事件驱动的 Reactor 模型:
客户端建立连接后,将 socket 注册到 IO 多路复用器(epoll)中。
socket 上有事件发生时,把事件放入队列。
单线程的事件处理器依次取出事件,进行连接应答、命令读取、命令执行、结果返回。
早期坚持单线程,是因为性能瓶颈主要在内存和网络上,而不是 CPU,且单线程可避免锁竞争,代码更简单。
随着网络硬件发展,单个主线程处理网络 IO 的读写逐渐成为瓶颈,因此Redis 6.0 引入 IO 多线程:多线程只负责 socket 的读写解析,命令执行仍由主线程完成。
5. Redis 的 key 和 value 最大能有多大?
单个 key 最大为 512MB 左右,单个 value 最大也是 512MB。实际生产中不建议存放过大的 key 或 value,因为会导致网络传输缓慢、内存分配压力大、删除时阻塞等问题,也就是所谓的「大 key」问题。
二、数据类型与底层数据结构篇
1. Redis 支持哪些数据类型?各自适用什么场景?
| 数据类型 | 特点 | 典型场景 |
|---|---|---|
| String | 最基础类型,可存字符串、整数、浮点数 | 缓存对象、计数器、分布式锁、Session 共享 |
| Hash | 适合存储对象,可单独更新某个字段 | 用户信息、购物车、商品详情 |
| List | 有序、可重复的双向链表 | 消息队列、最新消息列表、分页浏览 |
| Set | 无序、不可重复 | 标签、共同关注、抽奖去重、随机推荐 |
| ZSet | 带分数的有序集合 | 排行榜、延时队列、带权重的消息队列 |
高级数据结构:BitMap(签到、在线状态)、HyperLogLog(UV 统计)、GEO(附近的人)、Stream(消费者组模式的消息队列)。
2. 五种基本数据类型的底层数据结构分别是什么?
| 数据类型 | 底层实现 |
|---|---|
| String | SDS(简单动态字符串),整数会使用 int 编码 |
| List | QuickList(ZipList + LinkedList),Redis 7.0 中已用 ListPack 替代 ZipList |
| Hash | ListPack(或 ZipList)+ 哈希表,元素较少时为紧凑结构 |
| Set | 整数集合(intset)或哈希表,元素较少且全为整数时为 intset |
| ZSet | ListPack(或 ZipList)+ 跳表 skiplist 与哈希表组合 |
3. 什么是 SDS?相比 C 字符串有什么优势?
SDS 即 Simple Dynamic String(简单动态字符串),优势包括:
O(1) 获取长度:内部维护了 len 字段,不需要遍历到
\0计算长度。杜绝缓冲区溢出:拼接前检查空间是否足够,不够自动扩容。
减少内存重分配次数:采用空间预分配和惰性空间释放策略。
二进制安全:通过 len 判断字符串结束,可存任意二进制数据。
4. Redis 的 Hash 与 Java 的 HashMap 有什么区别?
扩容方式不同:Java HashMap 一次性完成 rehash,可能短暂停顿;Redis 采用渐进式 rehash,把过程分摊到多次操作中。
存储结构不同:Redis Hash 元素较少时采用紧凑的 ListPack 或 ZipList,元素多了才转为哈希表。
哈希函数不同:Redis 使用 SipHash 等算法,采用链地址法解决冲突。
渐进式 rehash 的核心思想是:维护 ht[0] 和 ht[1],rehash 过程中新数据写入 ht[1],每次增删改查时顺带把 ht[0] 中的一部分桶迁移到 ht[1],直到迁移完成。
5. ZSet 的跳表结构是怎样的?为什么用跳表而不用红黑树?
ZSet 在元素较多时使用「跳表 + 哈希表」结构:跳表用于按分数排序和范围查询,哈希表用于根据成员快速定位分数。
跳表是一种多层链表结构,每一层都是下一层的抽样索引,查找时从最高层开始向下跳跃,平均时间复杂度为 O(logN)。
选择跳表而非红黑树的原因:
跳表实现更简单,容易理解和维护,不容易写出 bug。
跳表支持快速的范围查询,红黑树的区间查找相对复杂。
跳表更符合 Redis 对简单、高效、可预测的追求。
6. 如何使用 Redis 实现排行榜?
排行榜是 ZSet 最经典的应用场景:
shell
# 增加或更新玩家分数 ZADD rank 100 player1 # 给玩家加分 ZINCRBY rank 50 player1 # 查询玩家排名(从 0 开始,降序) ZREVRANK rank player1 # 查询某个玩家的分数 ZSCORE rank player1 # 查询前 10 名 ZREVRANGE rank 0 9 WITHSCORES # 查询分数在 80 到 200 之间的玩家 ZRANGEBYSCORE rank 80 200 WITHSCORES
数据量大时,需使用ZREMRANGEBYRANK定期清理榜尾数据,避免 ZSet 无限增长。
7. 如何使用 Redis 实现一个简单的消息队列?
方式一:使用 List。生产者LPUSH/RPUSH,消费者BRPOP/BLPOP阻塞读取。
shell
# 生产者 LPUSH queue "message1" # 消费者(阻塞等待) BRPOP queue 0
优点是简单、可靠;缺点是没有消息确认机制,消费失败后消息会丢失。
方式二:使用发布订阅 Pub/Sub。SUBSCRIBE订阅,PUBLISH发布。缺点是消息无法持久化,断线期间消息丢失。
方式三:使用 Stream(Redis 5.0+)。支持消费者组、消息确认、消息回溯,是最完善的方案。
shell
# 生产消息 XADD mystream * field value # 创建消费者组 XGROUP CREATE mystream group1 0 # 消费者读取消息 XREADGROUP GROUP group1 consumer1 COUNT 1 STREAMS mystream > # 确认消息 XACK mystream group1 消息ID
实际生产中,要求不高的场景可用 List 实现简单异步解耦;可靠性要求高的场景建议使用 Stream 或专业消息中间件如 Kafka、RocketMQ。
三、持久化机制篇
1. Redis 有哪些持久化方式?分别是怎么实现的?
RDB(Redis DataBase):在指定时间间隔内,把内存中的数据集快照写入磁盘。fork 一个子进程负责写入临时文件,写完后替换旧 RDB 文件。触发方式包括手动
SAVE/BGSAVE和配置文件save条件自动触发。AOF(Append Only File):以追加日志形式记录每一条写命令。重启时通过重新执行 AOF 文件中的命令来恢复数据。三种刷盘策略:
always、everysec(默认)、no。混合持久化(Redis 4.0+):AOF 重写时,先使用 RDB 格式写入当前数据快照,再把重写期间的增量命令以 AOF 格式追加,兼顾快速加载和减少数据丢失。
2. RDB 和 AOF 各有什么优缺点?如何选择?
| 对比维度 | RDB | AOF |
|---|---|---|
| 数据完整性 | 可能丢失最后一次快照到宕机之间的数据 | 默认 everysec 最多丢失 1 秒数据,always 更安全 |
| 文件大小 | 紧凑的二进制文件,体积小 | 记录命令,文件通常更大 |
| 恢复速度 | 快,直接加载快照 | 慢,需要重放所有命令 |
| 对性能影响 | fork 子进程时有短暂开销,写时复制 | 默认每秒刷盘影响较小,always 影响较大 |
| 启动优先级 | 低 | 高,同时存在时优先加载 AOF |
官方建议两种都开启。对数据安全性要求高,优先保障 AOF;对恢复速度有要求且能容忍少量数据丢失,可同时开启 RDB。生产环境最常见的是「RDB 做冷备 + AOF everysec」的组合。
3. AOF 重写是什么?为什么要重写?
AOF 文件会随着写命令增加不断变大,文件过大会导致磁盘占用高、恢复慢。AOF 重写就是通过读取当前内存中的数据状态,生成一份新的、体积更小的命令文件来替换旧的 AOF 文件,相当于把多条冗余命令「压缩」成一条。
例如对 key 执行了 100 次INCR,原本 AOF 记录了 100 条 INCR 命令,重写后可能只记录一条SET命令。重写过程由 fork 出的子进程完成,期间新的写命令会同时写入 AOF 缓冲和重写缓冲,重写完成后把重写缓冲的命令追加到新文件中,再原子地替换旧文件。
4. Redis 重启时,RDB 和 AOF 同时存在会优先加载哪个?
会优先加载 AOF 文件。因为 AOF 默认采用 everysec 刷盘,更新频率通常更高,数据比 RDB 更完整。当 AOF 关闭或文件不存在时,Redis 才会加载 RDB 文件。因此生产环境中如果同时开启两种持久化,恢复时需要以 AOF 的结果为准。
四、过期删除与内存淘汰篇
1. Redis 的过期删除策略有哪些?
Redis 中设置了过期时间的 key 不会在过期瞬间被立即删除,而是通过「惰性删除 + 定期删除」两种策略配合完成清理:
惰性删除:客户端访问某个 key 时,先判断是否过期。已过期就删除并返回空,未过期则正常返回值。优点是节省 CPU;缺点是如果过期 key 一直不被访问,就会长期占用内存。
定期删除:Redis 内部默认每 100ms 左右执行一次过期扫描,随机抽取一批设置了过期时间的 key 判断是否过期,并根据过期比例决定是否继续本轮扫描。优点是内存释放更及时;缺点是扫描本身消耗 CPU。
为什么不用定时删除:如果为每个 key 都设置一个精确的过期定时器,当 key 数量很大时会产生大量定时事件,严重消耗 CPU,拖垮 Redis 的吞吐能力。
实际执行中,两种策略互为补充:惰性删除兜底读写路径,定期删除负责主动清理长期不被访问的过期 key,从而在 CPU 和内存之间取得平衡。
2. Redis 提供了哪些内存淘汰策略?分别是什么意思?
| 策略 | 含义 |
|---|---|
| noeviction | 不淘汰任何 key,内存不足时写入命令报错 |
| allkeys-lru | 所有 key 中淘汰最近最少使用的 |
| allkeys-lfu | 所有 key 中淘汰访问频率最低的 |
| allkeys-random | 所有 key 中随机淘汰 |
| volatile-lru | 设置了过期时间的 key 中淘汰最近最少使用的 |
| volatile-lfu | 设置了过期时间的 key 中淘汰访问频率最低的 |
| volatile-random | 设置了过期时间的 key 中随机淘汰 |
| volatile-ttl | 设置了过期时间的 key 中淘汰剩余生存时间最短的 |
LRU 和 LFU 的区别(高频考点):
LRU(Least Recently Used):关注「最近一次被访问的时间」,淘汰最久没有被访问的 key。假设最近访问过的数据更可能再次被访问。
LFU(Least Frequently Used):关注「一段时间内的访问频率」,淘汰访问次数最少的 key。更能保留长期高频的热点数据。
需要注意,Redis 实现的 LRU 是「近似 LRU」,通过随机采样评分的方式判断,默认每次采样 5 个 key,在精度和性能之间做折中。LFU 则根据访问次数和衰减时间综合计算热度。
3. 什么是缓存穿透?如何解决?
缓存穿透指查询一个根本不存在的数据,既不在缓存中,也不在数据库中。每次这样的恶意请求都会穿透缓存,给数据库造成巨大压力。
常见解决方案:
缓存空值:当数据库查询不到某个 key 时,仍然在缓存中写入一个带短过期时间的空对象。后续相同的非法 key 请求会命中这个空缓存,不再访问数据库。缺点是额外占用缓存空间,需要设置合理的过期时间。
布隆过滤器:在缓存之前加一层布隆过滤器,将所有合法 key 先登记到过滤器中。请求进来时先判断 key 是否可能存在,如果判断为不存在,则直接拒绝请求。布隆过滤器可能出现少量误判为「可能存在」,但不会漏判「一定不存在」。
使用 RedisBloom 模块可快速实现布隆过滤器:
shell
# 创建布隆过滤器,允许 0.01 的错误率,初始容量 100000 BF.RESERVE user:bf 0.01 100000 # 添加元素 BF.ADD user:bf 10086 # 判断元素是否存在 BF.EXISTS user:bf 10086 # 批量添加 BF.MADD user:bf 10010 10011 10012
如果不想引入额外模块,也可以使用 BitMap 自己实现简易布隆过滤器,核心思想是使用多个哈希函数将 key 映射到位图中的多个 bit 位,查询时只要有一个 bit 不为 1,就一定不存在。
4. 什么是缓存击穿?如何解决?
缓存击穿指某个热点 key 在过期瞬间,大量并发请求同时穿过缓存直接打到数据库。它与缓存穿透的区别是:击穿针对的是「一个确实存在的热点 key」,而穿透针对的是「根本不存在的 key」。
常见解决方案:
互斥锁:缓存失效后,只允许一个请求去数据库加载数据并回写缓存,其他请求等待锁释放后重新读取缓存。可以使用 Redis 的
SET key value NX EX实现简单的互斥锁。逻辑过期:给缓存 value 附带一个逻辑过期时间字段,而不是让 Redis 真正删除 key。读取时发现逻辑过期,先返回旧值,同时异步重建缓存,避免请求集中打到数据库。
永不过期:对极度核心的热点数据,不设置 Redis 过期时间,而是通过单独任务手动更新缓存,保证热点 key 始终存在。
5. 什么是缓存雪崩?如何解决?
缓存雪崩指大量缓存 key 在同一时间过期,或者 Redis 实例宕机,导致大量请求同时落到数据库,从而拖垮整个系统。它和缓存击穿的区别在于:击穿是单个热点 key 失效,雪崩是大量 key 同时失效。
解决方案主要包括:
过期时间加随机值:在原有基础上叠加随机值,把 key 的过期时间打散,避免同一时刻大量 key 同时过期。
多级缓存:本地缓存、Redis、数据库组成多级体系,即使 Redis 失效,本地缓存仍能抵挡一部分流量。
限流降级:对数据库访问进行限流和熔断,必要时返回兜底数据或默认文案。
提高高可用性:通过主从复制、哨兵、集群保证 Redis 本身不会轻易整体宕机。
6. 如何保证缓存与数据库的数据一致性?
核心矛盾在于:缓存和数据库是两套系统,任何跨系统的双写都无法做到严格意义上的强一致,最终只能选择更合适的最终一致性方案。
常见策略:
先更新数据库,再删除缓存:使用最广泛的方案。更新数据库成功后删除对应缓存 key,后续请求重新从数据库加载并写入缓存。它比「先删缓存再更新数据库」出现脏数据的概率更低。
延迟双删:更新数据库前先删除缓存,更新数据库后再延迟一段时间删除一次缓存,以清理并发读写产生的旧值。
通过 Binlog 异步更新缓存:数据库更新后,通过 Canal 等组件监听 Binlog,异步刷新或删除缓存。这样缓存更新与业务逻辑解耦,一致性更可控。
设置合适的过期时间兜底:即使更新链路出错,缓存也会因为过期而被删除,避免脏数据长期存在。
实际工作中,如果业务能容忍短暂不一致,推荐优先使用「先更新数据库,再删除缓存 + 缓存过期时间兜底」,简单且不易出错。
五、事务、管道与 Lua 篇
1. Redis 事务是怎么实现的?它真的具备原子性吗?
Redis 事务通过MULTI、EXEC、DISCARD、WATCH四个命令实现。核心流程是:执行MULTI开启事务,之后客户端发送的命令会被暂存到队列中,执行EXEC时按顺序一次性执行队列中的所有命令。
shell
# 开启事务 MULTI # 以下命令先进入队列,不会立即执行 SET user:1:name "Tom" INCR user:1:visit EXPIRE user:1:name 3600 # 执行队列中的所有命令 EXEC
Redis 事务并不具备传统数据库那样的强原子性,需要区分两类错误:
语法或参数错误:命令在入队时就被发现语法错误或参数错误,整个事务的所有命令都不会执行。
运行时错误:命令入队时合法,但执行时才发生错误(例如对 String 类型执行 List 命令),只有出错的命令失败,其他命令仍然继续执行。
也就是说,Redis 事务保证的是「命令按顺序排队、执行期间不被其他客户端命令插入」,而不是传统数据库中的回滚原子性。
2. WATCH 命令的作用是什么?
WATCH用来实现乐观锁。在事务开始前,客户端可以WATCH一个或多个 key。执行EXEC时,如果被监视的 key 在事务执行前被其他客户端修改过,整个事务会放弃执行并返回 nil。
shell
# 监视余额 key WATCH account:balance # 读取余额并判断 GET account:balance # 开启事务并扣减余额 MULTI DECRBY account:balance 100 EXEC
如果其他客户端在WATCH之后、EXEC之前修改了account:balance,本次EXEC会失败,客户端可以重试。这种方式常用于「先读后写」且不希望被并发修改覆盖的场景。
3. Pipeline 管道是什么?有什么作用?
Pipeline(管道)是一种批量发送命令、批量接收结果的机制。普通情况下,客户端每发送一条命令都要等待 Redis 返回结果后再发下一条,往返时间 RTT 浪费严重。使用 Pipeline 后,客户端可以把多条命令一次性发送给 Redis,Redis 依次执行后再一次性返回所有结果。
shell
# 一次性发送多条命令,而不是逐条等待 SET a 1 SET b 2 SET c 3 INCR a GET a
Pipeline 的核心价值是减少网络 RTT,而不是减少命令执行时间。它适合大量独立命令的批处理场景。注意 Pipeline 并不保证事务原子性,命令之间仍然可以被其他客户端命令穿插执行。
4. Redis 如何使用 Lua 脚本?它有什么优点?
Redis 从 2.6 版本开始支持 Lua 脚本,通过EVAL或EVALSHA执行。脚本在 Redis 服务端由单线程顺序执行,因此脚本内部的多条命令天然具备原子性,执行期间不会被其他客户端命令打断。
shell
# 语法:EVAL 脚本 numkeys key1 key2 ... arg1 arg2 ... EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue # 原子地扣减库存并判断结果 EVAL "if redis.call('GET', KEYS[1]) >= ARGV[1] then return redis.call('DECRBY', KEYS[1], ARGV[1]) else return -1 end" 1 stock:1001 1Lua 脚本的主要优点:
原子性:脚本中所有命令作为一个整体执行,不需要额外加锁。
减少网络开销:多条操作一次发送,减少 RTT。
逻辑复用:脚本可以缓存在 Redis 中,后续用
EVALSHA按 SHA1 执行,减少脚本重复传输。
使用 Lua 时要注意避免长耗时脚本,因为脚本执行期间整个 Redis 会阻塞。Redis 也提供了lua-time-limit等保护机制。
5. 事务、Pipeline 和 Lua 有什么区别?
| 对比维度 | 事务 | Pipeline | Lua 脚本 |
|---|---|---|---|
| 核心目的 | 保证命令排队按顺序执行 | 减少网络 RTT | 保证多条命令原子执行 |
| 是否原子 | 不保证回滚原子性 | 不保证原子性 | 保证原子性 |
| 执行期间是否阻塞其他命令 | EXEC 执行队列时不会被其他命令插入 | 命令之间可能被其他客户端命令穿插 | 脚本执行期间阻塞全部其他命令 |
| 典型场景 | 乐观锁、简单串联操作 | 批量读写、初始化数据 | 复杂条件判断、原子扣减、分布式锁释放 |
简单理解:Pipeline 解决「网络耗时」,事务解决「执行顺序」,Lua 解决「多条命令原子性」。
六、主从复制、哨兵与集群篇
1. Redis 主从复制的工作流程是怎样的?
主从复制用于实现数据冗余和读写分离。核心流程分为全量同步和增量同步两个阶段:
全量同步:从节点首次连接主节点时,执行
PSYNC ? -1请求全量同步。主节点执行BGSAVE生成 RDB 快照并发送给从节点,同时记录快照期间的写命令到缓冲区。从节点加载 RDB 后,主节点再把缓冲区的增量命令发送给从节点执行,使数据追平。增量同步:全量同步完成后,主节点后续的每一条写命令都会异步发送给从节点。从节点通过维护复制偏移量 offset 判断数据是否落后。如果连接断开后短时间内重连,并且偏移量仍在主节点的复制积压缓冲区范围内,就可以通过增量同步快速追平,而不需要再次全量复制。
Redis 2.8 之后用PSYNC替代了旧的SYNC,支持断线后的部分重同步;Redis 7.0 之后进一步演化出基于 replication id 的更完整的PSYNC实现。
2. 主从复制有哪些常见问题?
数据不一致:默认是异步复制,主节点写成功后不会等待从节点落盘。如果主节点突然宕机,尚未同步到从节点的数据会丢失。
复制风暴:当多个从节点同时向同一个主节点发起全量同步时,主节点需要多次执行
BGSAVE、多次传输 RDB,CPU、内存和网络压力巨大。可以通过「级联复制」让部分从节点挂到其他从节点上,减少主节点压力。循环复制:配置主从时如果拓扑出错,可能出现 A 复制 B、B 又复制 A 的循环,需要谨慎规划主从关系。
七、速查表与总结
| 主题 | 核心要点 |
|---|---|
| Redis 定位 | 内存数据库,非单纯缓存,单线程事件循环 + IO 多路复用 |
| 为什么快 | 内存、高效数据结构、单线程、IO 多路复用、Reactor 模型 |
| 数据类型 | String、Hash、List、Set、ZSet + BitMap、HyperLogLog、GEO、Stream |
| 底层结构 | SDS、QuickList、Dict、IntSet、SkipList、ListPack |
| 过期删除 | 惰性删除 + 定期删除(无定时删除) |
| 内存淘汰 | noeviction、allkeys/volatile + lru/lfu/random/ttl |
| RDB | 快照,恢复快,可能丢最近数据,fork 子进程 |
| AOF | 追加写命令,everysec 最多丢 1 秒,文件大恢复慢 |
| 混合持久化 | Redis 4.0+,RDB 快照 + AOF 增量,兼顾速度与安全 |
| 启动优先级 | 同时存在时优先加载 AOF |
| 缓存穿透 | 缓存空值、布隆过滤器 |
| 缓存击穿 | 互斥锁、逻辑过期、永不过期 |
| 缓存雪崩 | 过期时间加随机值、多级缓存、限流降级、高可用 |
| 缓存一致性 | 先更新数据库再删除缓存、延迟双删、Binlog 订阅 |
| 事务 | MULTI/EXEC/DISCARD/WATCH,不保证回滚原子性 |
| Pipeline | 批量发送减少 RTT,不保证原子性 |
| Lua | 服务端原子执行,减少 RTT,可缓存复用 |
| 主从复制 | 全量 + 增量,异步复制,可能存在复制风暴 |
总结:Redis 面试的主线可以浓缩为「基础 → 数据结构 → 持久化 → 过期与淘汰 → 缓存三大问题与一致性 → 事务/Pipeline/Lua → 高可用与集群」。面试回答时,先讲清原理,再结合项目经验说明选型与权衡,这样既能体现深度,也能让面试官相信你真正在生产环境中用过 Redis。