1. Redis 是单线程为什么还这么快?
Redis 快的本质是 内存 + IO 多路复用,单线程反而是加分项(避免了锁和切换开销),而不是拖后腿。
Redis 是基于内存操作,内存的访问速度是磁盘的 100 万倍。
Redis 的刷盘是异步的,不占用主线程。
Redis 的单线程采用的是 IO 多路复用。
单线程线程减少了上下文切换带来的开销和并发 Bug。
有高效的数据结构,如果 SDS、压缩列表、跳表等。
Redis 的性能瓶颈不在 CPU,而在 IO 和网络传输,Redis6.0 采用了多线程 IO 来处理网络连接。
2. RDB 和 AOF
RDB(Redis Database),使用 fork 子进程,定时对整个内存数据做快照,将二进制数据覆盖式写入 dump.rdb 文件,优点是体积小、恢复速度快、fork 子进程是有瞬间延迟,缺点是可能丢失最后一次快照后的数据。适合做冷备、灾难恢复、主从同步。
写时复制
仅在 fork 瞬间短暂停顿
自动触发(配置)
手动触发:SAVE、BGSAVE 命令
AOF(Append Only File),追加式地记录每一条写命令到 appendonlyaof 文件,优点数据安全,最多丢失 1s 数据(可配置),缺点是 aof 文件体积大(但可压缩),恢复速度慢。生产环境推荐使用混合持久化(Redis4.0 开启)。
每条写命令先追加到内存缓冲区
刷盘策略(fsync):always、everysec、no
AOF 重写:使用当前内存数据生成一份 aof 文件
混合持久化的 aof 文件:RDB 开头 + AOF 结尾,配置开启
3. Redis 实现分布式锁
锁是一种与共享资源分离的同步对象,多个并发操作者通过竞争获取该锁,以实现对资源的互斥访问。
所以,对锁的要求有:
获取和释放锁的操作必须是原子操作:防止出现死锁
锁必须有超时机制:防止死锁
持有锁的操作者需要在锁定期间内操作共享资源:锁续期问题
锁本身的高可用问题:RedLock 解决方案、Zookeeper
生产环境推荐使用 Redisson
可重入: Redisson 使用 hash 结构存储可实现锁的可重入(value + 1)
续期:看门狗机制实现锁的自动续期
Redis 实现分布式锁的问题:
原子操作:SETNX + EXPIRE 是非原子操作
SET NX EX + Lua 释放:是原子操作,但不可重入、不能续期
其他问题:
手动设置续期时间(
leaseTime) Redisson 的看门狗机制会失效Redis 单点故障、主从切换期间加锁,会导致锁丢失
4. Redis 缓存和数据库一致性问题
有四种常见方案:
先更新数据库,再更新缓存
并发更新会覆盖数据,导致数据错乱
先更新数据库,再删除缓存
删除操作时幂等的,推荐,可配合 TTL 兜底
“写”操作比“读”操作慢时会出现数据不一致(几乎不会出现)
先删除缓存,再更新数据库
并发读会写会旧值,需要配合延迟双删
订阅 binlog 日志,异步更新
最可靠,但增加维护成本
最终一致性
5. Redis 的 Pub/Sub 模式
Redis 的 Pub/Sub 是基于频道字典的实时消息广播机制,可以实现轻量化的消息队列,客户端可以通过频道订阅和模式订阅的方式订阅消息,Pub/Sub 的消息不能通过 RDB/AOF 持久化,消息发完即丢。
Redis5.0 引入 Stream 数据类型,可以实现消息持久化和 ACK 确认机制、消费者组,同组消费者消息只会消费一次。但是 Redis 毕竟是基于内存的,可以实现轻量级的消息队列,如果业务吞吐量高,得使用专业的 MQ。
6. Redis 的过期策略
针对设置了过期时间的 key,Redis 采用 定时删除+惰性删除 的策略来清理。
定期删除:每隔一段时间随机抽查一批 key,删除过期的。
惰性删除:访问时,发现过期才删除。
这么做的好处,避免大量 key 同时过期,CPU 飙升,惰性删除,过期 key 会长时间占用内存。
Redis 在每个数据库里面同时维护了两个字典:主字典、过期字典,两个字典采用相同的 key,指向同一个 value。
7. Redis 淘汰策略
针对所有 key,在内存不足时触发,常用策略 LRU 和 LFU。
8. Redis 事务和 Lua 脚本
Redis 事务的本质:命令排队、不支持回滚,Redis 事务 不是数据库意义上的 ACID 事务。
Redis 事务无法读取中间结果。
MULTI/EXEC:Redis 事务在执行过程中某条命令失败 不会回滚 已执行的命令,会继续执行后续命令。
生产环境优先选择 Lua 脚本,因为它既保证原子性又支持复杂逻辑。
WATCH 可以实现 乐观锁:在事务开启前监控某个 Key,如果这个 Key 在事务执行前被其他客户端修改了,整个事务会自动失败。但这仍然无法实现 "根据条件动态决定执行哪些命令"。
Lua 脚本可以完美解决事务无法读取中间结果的问题。
Pipeline:纯粹为了 减少网络往返(RTT),把多条命令打包一次性发送。各命令独立执行,不保证原子性。适合批量 SET、批量 GET 等场景。
WATCH 是 Redis 的 乐观锁 机制。在事务开启前用 WATCH 监控一个或多个 Key,如果在 EXEC 执行前这些 Key 被其他客户端修改了,整个事务会自动取消(返回 nil)。
事务:排队执行,不回滚,不读中间结果 —— "排队买票,不许反悔,不许看前面的人买了啥"。
Lua 脚本:原子执行 + 条件判断 + 读中间结果 —— "一个人包场,想看啥看啥,想买啥买啥"。
Pipeline:只管打包发送,不管原子性 —— "快递打包,一次寄出,各走各的"。
选型:简单排队用事务,复杂逻辑用 Lua,批量操作用 Pipeline。
9. 缓存击穿、缓存穿透、缓存雪崩
它们的触发场景:"查不到" vs "过期了" vs "集体过期"。
这三类问题本质上都是 "缓存失效 → 大量请求打到 DB" 的不同变体,需要从 缓存层、应用层、数据库层 多维度防御。
穿透是 "查的东西根本不存在",击穿是 "一个热点 Key 过期了",雪崩是 "一堆 Key 同时过期或 Redis 挂了"。三者本质都是缓存挡不住请求,DB 被压垮。
核心区别:穿透是 "没有",击穿是 "一个没了",雪崩是 "一堆没了"。
10. Redis 大 key 问题
大 Key 是指单个 Key 的 Value 占用内存过大,或者集合类元素过多的 Key。
大 Key 的核心危害是 阻塞 Redis 主线程(操作耗时长)和 网络拥塞(传输耗时久)。解决方案是 拆分(减小粒度)+ 异步删除(UNLINK)+ 预防(设置阈值)。
发现大 key:
redis-cli --bigkeys:只能找到每种数据类型最大的一个 Key,无法列出所有大 Key。
MEMORY USAGE:每个 Key 都要单独调用,大量 Key 时耗时较长。
DEBUG OBJECT:
RDB 分析工具
Key 的五种解决方案:
拆分:把一个大 Key 拆成多个小 Key,是最根本的解决方案。
压缩:存入前先压缩(GZIP / Snappy),减少 Value 体积。
部分读取:不要
HGETALL/SMEMBERS全量读取,改用HSCAN/HGET分批或按需读取。安全删除:不要用
DEL删除大 Key,用UNLINK(Redis 4.0+)异步删除,或用HSCAN+HDEL分批删除。懒删除配置:开启
lazyfree-lazy-expire yes,让过期删除也在后台线程异步执行。
Redis 大 Key 问题是指单个 Key 的 Value 过大或集合元素过多,会导致 阻塞主线程、网络拥塞、内存倾斜 等严重问题。发现大 Key 可以用 redis-cli --bigkeys、MEMORY USAGE、RDB 离线分析等手段。解决方案的核心是 拆分大 Key 为多个小 Key,配合压缩、部分读取、UNLINK 异步删除、开启 lazyfree 配置等手段。预防大于治疗,在设计和开发阶段就要控制 Key 的大小
11. Redis 热点 key 问题
热点 Key 是指被大量客户端频繁访问的 Key,其 QPS 远高于其他 Key,导致单个 Redis 节点成为性能瓶颈。
热点 Key 的核心问题是 访问集中在一个节点上,解决方案是 本地缓存(减少请求)+ 读写分离(分散读压力)+ Key 分片(分散到多个节点)。
热点 Key 在 Redis Cluster 中的危害:
单节点过载:热点 Key 集中在某个节点上,该节点的 CPU、网卡带宽被瞬间打满,而其他节点可能很空闲。
影响其他业务:同一节点上的其他 Key 也会因为资源被占满而响应变慢,产生 "邻居效应"。
主从同步延迟:如果热点 Key 涉及写操作,主节点压力大时,主从复制延迟增大,读从节点可能拿到旧数据。
如何发现热点 key
redis-cli --hotkeys(Redis 4.0+)MONITOR命令(临时排查)业务层面监控
热点 Key 的四种解决方案:
本地缓存(最优先):在应用服务器本地缓存一份热点数据,请求直接从本地内存返回,不经过 Redis,大幅降低 Redis 访问压力。
读写分离:增加从节点,读请求分散到多个从节点,热点 Key 的读压力被多个节点分担,适合读多写少的场景。
Key 分片:将一个热点 Key 复制为 N 个副本,分布在不同节点上,读请求随机选择一个副本读取。
限流 + 熔断:对热点 Key 的访问频率进行限流,超过阈值的请求直接返回降级数据,保护 Redis 和 DB 不被压垮。
Redis 热点 Key 是指被大量客户端频繁访问的 Key,会导致单个节点 CPU 和网络被打满。发现热点 Key 可以用 --hotkeys、MONITOR、业务层监控等手段。解决方案的核心是 分散访问压力:优先使用本地缓存(Caffeine)挡住大部分请求,配合 Key 分片或读写分离分散 Redis 的读压力,最后用限流降级兜底保命。
12. Redis 的集群模式
Redis 有 3 种 集群模式,按演进顺序:
一句话结论:主从复制解决 “数据备份和读扩展”,哨兵在主从基础上解决 “自动故障转移”,Cluster 在哨兵基础上解决 “水平扩展(写扩展 + 存储扩展)”。生产环境 大规模用 Cluster,中小规模用 Sentinel + 主从。
主从复制
主从复制的两种同步方式:
全量同步:Slave 首次连接 Master 或断开时间过长时触发。Master 执行
BGSAVE生成 RDB 快照发给 Slave,Slave 清空旧数据后加载 RDB。数据量大时耗时长,会阻塞 Master。增量同步:Slave 短暂断开后重连时触发。Master 检查 Slave 的复制偏移量(offset)是否还在
repl_backlog(环形缓冲区)中,如果在就只发送增量数据,否则退化为全量同步。关键参数:
repl-backlog-size控制环形缓冲区大小,默认 1MB。如果断开期间 Master 写入的数据超过缓冲区大小,就无法增量同步,必须全量。生产环境建议调大(如 256MB)。
主从复制最大的问题是 Master 宕机需要人工切换。哨兵模式就是为了解决这个问题。
哨兵模式
哨兵模式的架构:
Sentinel 是独立进程:不存储数据,只负责监控、故障转移和通知。通常部署 3 个以上(奇数个),保证高可用。
持续监控:Sentinel 每秒向 Master 和 Slave 发送
PING,如果节点在指定时间内没有回复,就标记为主观下线。自动故障转移:Master 被判定客观下线后,Sentinel 自动从 Slave 中选举一个晋升为新 Master,并通知其他 Slave 和客户端。
故障转移流程(面试必问):
主观下线(SDOWN):单个 Sentinel 认为 Master 没有响应。可能只是网络抖动,不一定真的挂了。
客观下线(ODOWN):超过
quorum(半数)个 Sentinel 都认为 Master 没响应,确认为真的挂了。选举 Leader Sentinel:Sentinel 集群通过 Raft 算法选出一个 Leader 来执行故障转移。
选举新 Master:从 Slave 中按优先级选择 ——
replica-priority最小的优先,相同时选复制偏移量最大的(数据最新),再相同选run_id最小的。切换:新 Master 执行
SLAVEOF NO ONE断开复制,其他 Slave 指向新 Master,Sentinel 通知客户端新地址。
主从和哨兵的写操作都在单个 Master 上,无法水平扩展。当数据量或 QPS 超过单节点上限时,就需要 Cluster。
Cluster 集群
Redis Cluster 的分片架构:
16384 个 Hash Slot:Cluster 将所有数据划分为 16384 个 slot,分配给不同的 Master 节点。每个 Master 负责一部分 slot。
路由机制:客户端发送命令时,先对 key 做
CRC16(key) % 16384计算 slot 编号,再路由到负责该 slot 的 Master 节点。每个 Master 配备 Slave:保证高可用。Master 宕机时,Slave 自动晋升为新 Master。
Cluster 的五个核心机制:
Gossip 协议:节点之间定期交换状态信息(各自负责的 slot、节点存活状态等),去中心化,没有单点。
MOVED 重定向:客户端请求到了错误的节点,该节点返回
MOVED指令告诉客户端正确的节点地址。客户端会缓存这个映射关系,下次直接路由。ASK 重定向:slot 迁移期间的临时重定向。
MOVED表示 slot 已永久迁移,ASK表示正在迁移中临时重定向。故障检测与转移:节点互相 PING 检测存活,超过半数 Master 认为某节点下线,就触发故障转移,该节点的 Slave 晋升为新 Master。
Hash Tag:用
{key}语法强制多个 Key 路由到同一 slot。比如{user}:name和{user}:age会落在同一个 slot 上,这样可以对它们做MGET或 Lua 脚本操作。
三种集群模式的核心差异:
数据分片:只有 Cluster 支持数据分片(16384 slot),主从和哨兵的所有数据都在单个 Master 上。
高可用:主从复制需要人工切换,哨兵和 Cluster 都支持自动故障转移。
写扩展:主从和哨兵的写操作只能在单个 Master 上,Cluster 通过分片实现了写扩展。
存储扩展:主从和哨兵的存储受单机内存限制,Cluster 通过分片可以水平扩展存储容量。
复杂度:主从最简单,哨兵中等,Cluster 最复杂(需要考虑 slot 迁移、跨 slot 操作等)。
Cluster 的主要限制:
多 Key 操作限制:
MGET、MSET等批量操作要求所有 Key 在同一 slot 上。解决方案是使用 Hash Tag(如{user}:name、{user}:age)。Lua 脚本限制:Lua 脚本操作的所有 Key 必须在同一 slot。
事务限制:
MULTI/EXEC事务中的 Key 也必须在同一 slot。最小节点数:官方推荐至少 6 个节点(3 主 3 从),保证 slot 分布和高可用。
数据一致性:主从复制是异步的,Master 宕机时可能丢失少量尚未同步的数据。Cluster 追求的是高可用 + 最终一致性,不是强一致性。
客户端连接 Cluster 需要注意什么?
客户端需要支持 Cluster 协议(如 JedisCluster、Lettuce)。连接时不需要知道所有节点地址,只需要一个或几个节点地址,客户端会自动发现集群拓扑。请求到错误节点时会收到 MOVED 重定向,客户端需要自动重试。智能客户端会缓存 slot → node 映射关系,后续请求直接路由。
Redis 有三种集群模式:主从复制 实现数据冗余和读写分离;哨兵模式 在主从基础上增加自动故障转移;Cluster 模式 通过 16384 slot 分片实现水平扩展。生产环境大规模场景用 Cluster(分片 + 高可用),中小规模用 Sentinel + 主从(高可用)。Cluster 的核心限制是跨 slot 操作需使用 Hash Tag,且异步复制不保证强一致性。
13. Redis 集群脑裂问题
脑裂(Split Brain) 是指 Redis 集群中,由于 网络分区 导致出现了 两个 Master 同时接受写入的情况。
网络分区导致哨兵/Cluster 认为旧 Master 宕机,选举出新 Master,但旧 Master 实际还在运行,客户端同时向新旧两个 Master 写入数据,导致数据冲突和丢失,哨兵模式、Cluster 模式都可能发生,可配置 min-slaves-to-write + min-slaves-max-lag,让 Master 在从节点不足时 拒绝写入
脑裂的本质是网络分区导致 "一主变两主",解决思路是让旧 Master 在与从节点失联后 主动拒绝写入,从源头切断数据不一致的可能。
脑裂的三大危害:
数据丢失(最严重):网络恢复后,旧 Master 被降级为 Slave,会清空自身数据并从新 Master 全量同步。脑裂期间写入旧 Master 的所有数据都会 不可逆地丢失。
数据不一致:同一个 Key 可能被不同客户端分别写入新旧 Master,最终以新 Master 的数据为准,旧数据被覆盖。
客户端混乱:不同客户端连接到不同 Master,读到不一致的数据,业务逻辑出错。
脑裂本质是 CP vs AP 的取舍。Redis 选择的是 AP(可用性优先)——故障转移后新 Master 立即服务,不等待旧 Master 的数据同步。配置 min-slaves-to-write 是在向 CP(一致性优先) 倾斜——宁可牺牲可用性(拒绝写入),也要保证数据一致性。这是一个工程上的权衡。
Redis 脑裂是网络分区导致出现两个 Master 同时接受写入的问题,网络恢复后旧 Master 降级为 Slave 会导致数据丢失。最核心的解决方案 是配置 min-slaves-to-write + min-slaves-max-lag,让 Master 在从节点不可达时主动拒绝写入。辅助手段包括调大 Sentinel 超时时间、增大 quorum 值、分散部署 Sentinel 节点,以及建立完善的监控告警机制。
14. Redis 数据分片
Redis 数据分片是指 将数据按照一定规则分散存储到多个 Redis 节点上,从而突破单节点的内存、QPS 和网络带宽限制。
一句话结论:数据分片是解决 Redis 单机瓶颈的关键手段。早期用客户端分片或 Twemproxy/Codis 等代理方案,现在 Redis Cluster(服务端分片)是主流方案,通过 16384 个 Hash Slot 实现自动分片和故障转移。
追问一:为什么 Cluster 用 16384 个 slot,而不是 65536 或其他数字?
核心原因是 Gossip 协议的心跳包大小。节点之间定期交换 slot 映射信息,16384 个 slot 只需 2KB 的 bitmap 就能表示。如果用 65536 个 slot,bitmap 就需要 8KB,心跳包变大,浪费带宽。此外 Redis 作者 antirez 认为集群节点超过 1000 个时性能就不理想了,16384 完全够用。
追问二:Cluster 扩缩容时数据怎么迁移?
扩容时,先加入新节点,然后从现有节点 迁移部分 slot 到新节点。迁移过程中使用 ASK 重定向保证请求正常路由。缩容则反过来,先把 slot 迁走再下线节点。整个过程中集群正常服务,不会停机。
追问三:客户端分片中 "一致性哈希" 和 "取模" 有什么区别?
取模(hash(key) % N)在节点数 N 变化时,大部分 key 的映射关系都会改变,导致大面积缓存失效。一致性哈希将节点映射到 0~2^32 的环上,key 也映射到环上,顺时针找最近的节点。增减节点时只影响相邻节点的数据,迁移量小。Redis Cluster 没有使用一致性哈希,而是用了 固定 slot 数(16384)+ 哈希取模,因为 slot 数固定不变,节点变化只影响 slot 的归属映射。
Redis 数据分片是将数据分散到多个节点以突破单机瓶颈的技术,有客户端分片、代理分片(Twemproxy/Codis)和服务端分片(Redis Cluster)三种方式。生产环境推荐 Redis Cluster,通过 16384 个 Hash Slot 实现自动分片、路由和故障转移。核心路由机制是 CRC16(key) % 16384,配合 MOVED/ASK 重定向和 Hash Tag 解决跨节点操作问题。
评论区