一、Redis 核心进阶原理(高频重点)
1. Redis 单线程模型详解(非基础概念,深入原理)
核心知识点
Redis 核心 IO 模块采用单线程模型(仅1个主线程处理客户端读写请求),但依托 IO 多路复用机制、内存操作的高效性,实现高并发处理;需明确:单线程≠单进程,持久化、集群同步等操作由独立子进程/线程完成。
原理详解
(1)单线程模型的核心构成
Redis 启动后,会创建一个主线程,负责处理所有客户端的连接、读写请求、命令执行,同时维护一个「事件循环(Event Loop)」,核心依赖3个关键组件:
-
文件事件处理器(File Event Handler):负责监听客户端的连接(Socket 连接)和读写事件(请求发送、响应返回),基于 IO 多路复用机制实现;
-
时间事件处理器(Time Event Handler):负责处理定时任务(如过期键删除、AOF 重写触发、RDB 快照触发);
-
命令处理器(Command Processor):负责解析客户端发送的命令,执行对应的操作(如数据读写、结构修改),并返回响应结果。
(2)单线程高并发的核心原理——IO 多路复用
IO 多路复用的核心是「一个线程监听多个 Socket 连接」,避免单线程阻塞在某个客户端的 IO 操作上,Redis 底层根据操作系统不同,自动选择对应的 IO 多路复用机制:
-
Linux 系统:采用 epoll(最优选择),基于事件驱动,通过红黑树管理监听的 Socket,支持水平触发(LT)和边缘触发(ET),仅当 Socket 有数据可读/可写时,才通知主线程处理,无连接时主线程阻塞等待,开销极低;
-
BSD 系统:采用 kqueue,功能与 epoll 类似,效率接近;
-
Windows 系统:采用 select,效率较低(监听的 Socket 数量有限,且需轮询所有 Socket 判断状态)。
具体流程:
-
主线程将所有客户端 Socket 注册到 IO 多路复用器(如 epoll);
-
主线程阻塞等待 IO 多路复用器通知,此时不消耗 CPU 资源;
-
当某个 Socket 有数据(客户端发送请求)或可写(需返回响应)时,IO 多路复用器通知主线程;
-
主线程处理该 Socket 的请求(解析命令、执行操作),处理完成后返回响应,再回到阻塞等待状态。
(3)单线程模型的优势与局限
优势:
-
无上下文切换开销:单线程无需在多个线程间切换,减少 CPU 消耗;
-
无锁竞争:单线程操作数据,无需加锁(如 Java 多线程的 synchronized),避免死锁和锁开销;
-
内存操作高效:Redis 所有核心操作均基于内存,单线程足以最大化利用 CPU 资源(内存操作速度远快于 CPU 处理速度)。
局限:
-
单线程处理所有请求,若某个命令执行时间过长(如 keys *、hgetall 海量数据),会阻塞所有后续请求,导致服务卡顿(解决方案:避免执行慢命令,用 scan 替代 keys);
-
无法利用多核 CPU 资源(解决方案:部署 Redis 集群,让多个节点分别占用不同 CPU 核心)。
(4)延伸补充:Redis 哪些操作不是单线程?
单线程仅针对「客户端 IO 处理和命令执行」,以下操作由独立子进程/线程完成,不阻塞主线程:
-
持久化操作:bgsave(RDB 异步快照)、bgrewriteaof(AOF 异步重写),由子进程执行;
-
集群同步:主从复制中的 RDB 发送、增量命令同步,由独立线程执行;
-
过期键删除:惰性删除由主线程执行(不阻塞),定期删除由独立线程执行。
2. Redis 过期键删除机制(原理+实战)
核心知识点
Redis 支持为键设置过期时间(expire 命令),过期键删除机制采用「惰性删除+定期删除+内存淘汰」三者结合的方式,既保证数据时效性,又避免删除操作影响服务性能。
原理详解
(1)三种删除机制的核心原理
1. 惰性删除(Lazy Delete)—— 被动删除
-
原理:Redis 不主动删除过期键,仅当客户端「访问该键」时,才检查该键是否过期;若过期,则删除该键并返回 null;若未过期,则正常返回键值。
-
核心优势:不占用额外 CPU 资源,仅在访问时触发删除,对正常服务无影响;
-
核心缺点:若过期键长期不被访问,会一直占用内存,导致内存泄漏(需配合定期删除和内存淘汰机制解决)。
2. 定期删除(Periodic Delete)—— 主动删除
- 原理:Redis 主线程每隔一段时间(默认 100ms),会执行一次定期删除操作,流程如下:
-
从过期键字典(存储所有设置了过期时间的键)中,随机采样 N 个键(默认 20 个);
-
检查采样的键是否过期,删除所有过期的键;
-
若本次删除的过期键数量占采样总数的比例 ≥ 25%,则重复步骤 1-2(继续采样删除);若比例 < 25%,则停止本次定期删除。
-
核心优势:主动清理部分过期键,减少内存泄漏风险,且通过“采样+比例控制”,避免删除操作占用过多 CPU 资源(防止阻塞主线程);
-
核心参数:通过「hz」配置定期删除的频率(默认 hz=10,即每秒执行 10 次,取值范围 1-500,hz 越大,删除越频繁,CPU 开销越大)。
3. 内存淘汰(Memory Eviction)—— 兜底机制
-
原理:当 Redis 内存达到 maxmemory(最大内存)时,无论键是否过期,都会触发内存淘汰策略,删除部分键释放内存(详细淘汰策略见内存管理模块,此处重点说明与过期删除的关联);
-
关联逻辑:若内存淘汰策略为「volatile-lru/volatile-ttl/volatile-random/volatile-lfu」,则优先淘汰过期键;若为「allkeys-lru/allkeys-random/allkeys-lfu」,则不分过期与否,优先淘汰符合条件的键;若为「noeviction」,则不淘汰任何键,拒绝写操作。
(2)过期键删除的实战问题
- 问题1:为什么过期键已经删除,内存占用还是很高?
原因:惰性删除未触发(过期键未被访问)、定期删除未采样到该键,导致过期键长期驻留内存;解决方案:调整 hz 参数(适当增大),或手动执行「expirescan」命令,主动扫描并删除过期键。
- 问题2:过期键删除会阻塞主线程吗?
惰性删除:不会阻塞(仅在访问时触发,操作时间极短);定期删除:可能短暂阻塞(若采样后过期键比例高,会重复采样删除),可通过调整 hz 和采样数量,平衡删除效果和性能。
3. Redis 事务机制(原理+缺陷+替代方案)
核心知识点
Redis 事务是一组命令的集合,通过 MULTI(开启事务)、EXEC(执行事务)、DISCARD(取消事务)、WATCH(乐观锁)命令实现,核心保证「原子性(部分原子性)、隔离性」,不保证一致性和持久性。
原理详解
(1)事务的核心流程
-
开启事务:客户端发送 MULTI 命令,Redis 主线程标记当前客户端进入事务状态,后续发送的命令不会立即执行,而是存入「事务队列」;
-
入队命令:客户端发送的每一条命令(如 set、hset、incr),Redis 都会检查命令格式是否正确;若格式错误,会立即返回错误,且后续 EXEC 执行时,整个事务会被取消;
-
执行事务:客户端发送 EXEC 命令,Redis 主线程依次执行事务队列中的所有命令,执行完成后,返回所有命令的响应结果;
-
取消事务:客户端发送 DISCARD 命令,Redis 清空事务队列,取消当前事务状态,客户端恢复正常模式。
(2)事务的四大特性(与关系型数据库对比)
1. 原子性:部分原子性(Redis 事务的核心缺陷)
-
关系型数据库(如 MySQL):事务中一条命令失败,所有命令都回滚,完全原子性;
-
Redis:仅当命令「格式错误」(如语法错误)时,事务会被取消,所有命令都不执行;若命令「逻辑错误」(如对 String 执行 hset 操作),错误命令会执行失败,其他命令正常执行,不会回滚。
示例:
MULTI → set key1 1 → hset key1 field 2(逻辑错误,key1 是 String 类型)→ EXEC;
结果:set key1 1 执行成功,hset 执行失败,key1 仍为 1,不会回滚。
2. 隔离性:串行化隔离(无并发问题)
Redis 是单线程模型,事务执行期间,其他客户端的命令会被阻塞,直到事务执行完成,因此事务之间不会出现并发问题,天然满足串行化隔离级别(最高隔离级别)。
3. 一致性:不保证
一致性是指事务执行前后,数据符合预期规则;Redis 事务无法保证一致性,原因:
-
逻辑错误导致部分命令失败,数据可能不符合预期;
-
事务执行期间,Redis 崩溃,若未开启持久化,数据会丢失;若开启持久化,可能出现数据不一致(如事务执行一半,Redis 崩溃,部分命令未落盘)。
4. 持久性:不保证
持久性依赖 Redis 持久化机制(RDB/AOF),事务执行完成后,若 Redis 立即崩溃,且未开启持久化,数据会丢失;即使开启 AOF,若 appendfsync 配置为 everysec 或 no,也可能丢失数据。
(3)WATCH 命令(乐观锁,事务增强)
-
原理:WATCH 命令用于监控一个或多个键,若在事务执行前(EXEC 之前),被监控的键被其他客户端修改,则事务会被取消(返回 nil),执行失败;若未被修改,则正常执行事务。
-
流程示例:
WATCH key1 → MULTI → set key1 2 → EXEC;
若在 MULTI 和 EXEC 之间,其他客户端修改了 key1,则 EXEC 执行失败,返回 nil;若未修改,则执行成功。
- 注意:WATCH 仅在当前事务有效,事务执行完成(EXEC/DISCARD)后,监控自动取消;若需重新监控,需再次执行 WATCH 命令。
(4)事务的缺陷与替代方案
缺陷:部分原子性、不保证一致性和持久性,无法应对复杂的并发场景。
替代方案:
-
简单并发场景:用 WATCH + 事务,保证键不被并发修改;
-
复杂原子性场景:用 Lua 脚本(Redis 保证 Lua 脚本中所有命令原子执行,要么全部成功,要么全部失败);
-
分布式场景:用 Redis 分布式锁 + Lua 脚本,保证跨客户端的原子操作。
4. Redis Lua 脚本(原理+应用场景)
核心知识点
Lua 是一种轻量级脚本语言,Redis 内置 Lua 解释器,支持执行 Lua 脚本,核心优势是「原子性执行」,可替代复杂事务,实现自定义命令和高效并发控制。
原理详解
(1)Lua 脚本的原子性原理
Redis 执行 Lua 脚本时,会将整个脚本作为一个整体,交给主线程执行,执行期间,会阻塞所有其他客户端的命令请求(单线程模型特性),直到脚本执行完成,因此 Lua 脚本中的所有命令,要么全部成功,要么全部失败,实现完全原子性。
注意:Lua 脚本执行时间不宜过长(建议不超过 100ms),否则会阻塞主线程,导致服务卡顿;若脚本执行时间过长,Redis 会自动终止脚本(可通过「lua-time-limit」配置,默认 5000ms)。
(2)Lua 脚本的核心命令
- EVAL 命令:执行 Lua 脚本,格式:EVAL "脚本内容" 键数量 键1 键2 ... 参数1 参数2 ...
示例:EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 key1 value1 → 等价于 set key1 value1;
-
EVALSHA 命令:执行已缓存的 Lua 脚本(避免重复传输脚本,提升性能),格式:EVALSHA 脚本哈希值 键数量 键1 键2 ...;
-
SCRIPT LOAD 命令:将 Lua 脚本缓存到 Redis 中,返回脚本哈希值,后续可通过 EVALSHA 执行;
-
SCRIPT KILL 命令:终止正在执行的 Lua 脚本(仅当脚本未执行写操作时可用);
-
SCRIPT FLUSH 命令:清空所有缓存的 Lua 脚本。
(3)Lua 脚本的核心应用场景
- 复杂原子操作:替代 Redis 事务,实现完全原子性,如“先判断键是否存在,存在则修改,不存在则新增”;
示例脚本:判断 key1 是否存在,存在则 incr,不存在则 set key1 1:
"if redis.call('exists', KEYS[1]) == 1 then return redis.call('incr', KEYS[1]) else return redis.call('set', KEYS[1], 1) end"
-
分布式锁优化:实现更安全的分布式锁(如自动释放锁、防止死锁),结合 SET NX EX 命令和 Lua 脚本,避免锁竞争;
-
自定义命令:将常用的多个 Redis 命令组合成一个 Lua 脚本,作为自定义命令使用,减少网络传输次数(如批量查询多个键的值,避免多次发送 get 命令);
-
限流逻辑:实现复杂的限流规则(如令牌桶限流、漏桶限流),通过 Lua 脚本原子执行限流逻辑,避免并发问题。
(4)延伸补充:Lua 脚本的注意事项
-
避免在脚本中执行慢命令(如 keys *、scan 全量扫描),防止阻塞主线程;
-
脚本中不可使用全局变量,避免影响其他脚本执行;
-
若脚本执行写操作,SCRIPT KILL 命令无法终止,只能重启 Redis(因此需严格控制脚本执行时间)。
二、Redis 实战高频问题(非基础/高可用,重点考察落地能力)
1. Redis 分布式锁的实现原理、缺陷及优化方案(高频实战)
核心知识点
Redis 分布式锁是分布式系统中解决并发竞争的核心工具,基于 SET 命令的 NX(不存在则设置)和 EX(过期时间)参数实现,核心要求:互斥性、安全性、可用性、可重入性。
原理详解
(1)基础实现(SET NX EX 命令)
- 核心命令:SET lock_key unique_value NX EX expire_time(单位:秒)
各参数含义:
-
lock_key:分布式锁的键名(如 lock:order:123);
-
unique_value:唯一值(如 UUID、客户端 ID),用于标识锁的持有者,避免误释放他人的锁;
-
NX:Only set the key if it does not already exist(仅当键不存在时,才设置成功,保证互斥性);
-
EX:Set the specified expire time in seconds(设置过期时间,避免死锁,即使客户端崩溃,锁也会自动释放)。
-
实现流程:
-
客户端请求获取锁:执行 SET lock_key unique_value NX EX 30;
-
若返回 OK,说明获取锁成功,执行后续业务逻辑;
-
业务逻辑执行完成后,释放锁:通过 Lua 脚本删除锁(避免误释放),脚本如下:
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
(判断锁的持有者是否是当前客户端,若是则删除,否则不操作);
- 若获取锁失败,客户端等待一段时间(如 50ms),重新尝试获取,直到获取成功或超时。
(2)基础实现的缺陷
-
锁过期问题:若业务逻辑执行时间超过锁的过期时间,锁会自动释放,其他客户端会获取到锁,导致并发问题(如订单重复提交);
-
单点故障问题:若 Redis 是单节点,节点崩溃后,所有客户端无法获取锁,影响服务可用性;
-
不可重入问题:同一客户端获取锁后,再次请求获取锁会失败(无法重入,不适合嵌套业务场景);
-
锁释放问题:若客户端获取锁后,网络波动导致无法释放锁,锁会在过期后自动释放,但期间会影响其他客户端获取锁。
(3)优化方案(解决上述缺陷)
1. 解决锁过期问题:锁续期(Watch Dog 机制)
原理:客户端获取锁后,启动一个后台线程(Watch Dog),每隔一段时间(如 10s),检查当前客户端是否还持有锁;若持有,则将锁的过期时间延长(如延长至 30s),直到业务逻辑执行完成,主动释放锁。
实现:Java 中可通过 Redisson 框架实现(Redisson 内置 Watch Dog 机制),无需手动编写续期逻辑。
2. 解决单点故障问题:Redis 集群(主从/哨兵/Cluster)
原理:将 Redis 部署为集群(如 3 主 3 从),客户端获取锁时,需向多个主节点发送 SET NX EX 命令,只有超过半数主节点返回 OK,才视为获取锁成功(类似分布式一致性算法),避免单节点故障导致锁不可用。
标准实现:RedLock 算法(Redis 官方推荐),核心是“多节点锁”,保证高可用。
3. 解决不可重入问题:可重入锁实现
原理:在锁的 value 中存储“客户端 ID + 重入次数”,当客户端再次获取锁时,若锁的 value 中的客户端 ID 是当前客户端,则重入次数 +1;释放锁时,重入次数 -1,当重入次数为 0 时,删除锁。
实现:通过 Lua 脚本实现,Redisson 框架也内置了可重入锁。
4. 解决锁释放问题:优化释放逻辑+超时控制
-
严格使用 Lua 脚本释放锁,确保只有锁的持有者才能释放;
-
客户端获取锁时,设置合理的超时时间(结合业务逻辑执行时间,预留一定冗余);
-
业务逻辑执行超时后,主动释放锁(即使锁已过期,也执行释放操作,避免残留锁)。
(4)延伸补充:生产环境推荐方案
不建议手动编写分布式锁逻辑(易出现缺陷),推荐使用 Redisson 框架,该框架封装了 Redis 分布式锁的各种实现(可重入锁、公平锁、读写锁、RedLock 等),内置 Watch Dog 续期、集群适配等功能,开箱即用,稳定性高。
2. Redis 缓存穿透、缓存击穿、缓存雪崩(原理+解决方案)
核心知识点
三者是 Redis 缓存使用中的常见问题,均会导致缓存失效,大量请求穿透到数据库,造成数据库压力过大,甚至宕机;核心区别在于“失效的原因和范围”不同。
原理详解(分问题拆解)
1. 缓存穿透(Cache Penetration)
-
定义:客户端请求的「不存在的数据」(如 ID 为 -1 的用户),缓存中没有,数据库中也没有,导致每次请求都穿透到数据库,大量请求会压垮数据库。
-
产生原因:
-
恶意请求(如黑客伪造不存在的 ID,批量请求);
-
业务逻辑问题(如查询不存在的商品、用户)。
- 解决方案(优先级从高到低):
-
空值缓存:对于不存在的数据,缓存一个空值(如 ""、null),设置较短的过期时间(如 5-10 分钟),避免后续请求穿透到数据库;
-
布隆过滤器(Bloom Filter):提前将所有存在的合法 key(如所有用户 ID、商品 ID)存入布隆过滤器,客户端请求时,先通过布隆过滤器判断 key 是否存在;若不存在,直接返回,不请求缓存和数据库;若存在,再请求缓存和数据库;
原理:布隆过滤器是一种概率数据结构,通过多个哈希函数将 key 映射到二进制数组中,判断 key 是否存在(存在误判率,可通过调整数组大小和哈希函数数量降低);
- 接口限流:对恶意请求进行限流(如 IP 限流、接口请求频率限制),避免大量恶意请求穿透。
2. 缓存击穿(Cache Breakdown)
-
定义:客户端请求的「热点数据」(如热门商品、热门文章),缓存中存在,但缓存突然过期,此时大量请求会同时穿透到数据库,造成数据库瞬时压力过大。
-
产生原因:热点数据的缓存过期时间到了,且此时有大量并发请求。
-
解决方案(优先级从高到低):
-
热点数据永不过期:对于核心热点数据(如首页热门商品),不设置过期时间,由业务逻辑手动更新缓存(如商品信息修改时,同步更新缓存);
-
缓存续期:在热点数据过期前,主动延长其过期时间(如通过定时任务,每隔一段时间检查热点数据的过期时间,若即将过期,则延长);
-
互斥锁:当缓存过期时,只有一个客户端能获取到锁,去数据库查询数据并更新缓存,其他客户端等待,获取锁后再从缓存中获取数据(避免大量请求穿透);
实现:用 Redis 分布式锁(SET NX EX),查询数据库前获取锁,查询完成后更新缓存并释放锁。
3. 缓存雪崩(Cache Avalanche)
-
定义:大量缓存数据「同时过期」,或 Redis 集群宕机,导致大量请求同时穿透到数据库,造成数据库崩溃,服务不可用。
-
产生原因:
-
缓存数据设置了相同的过期时间(如批量导入的商品,都设置了 24 小时过期);
-
Redis 集群故障(如主节点宕机,从节点未及时切换,导致缓存不可用)。
- 解决方案(分场景):
场景1:缓存数据同时过期
-
过期时间随机化:给每个缓存数据的过期时间加上一个随机值(如 1-300 秒),避免大量数据同时过期;
-
分批次过期:将缓存数据分批次设置不同的过期时间(如第一批 24 小时,第二批 24.5 小时,第三批 25 小时),分散过期压力。
场景2:Redis 集群宕机
-
高可用部署:将 Redis 部署为哨兵集群或 Cluster 集群,确保主节点故障时,从节点能自动切换,保证缓存可用性;
-
降级熔断:当 Redis 不可用时,通过降级策略(如返回默认数据、提示服务繁忙),避免大量请求穿透到数据库;
-
本地缓存:在应用服务器本地设置一层缓存(如 Caffeine),缓存核心热点数据,当 Redis 宕机时,可从本地缓存获取数据,缓解数据库压力。
3. Redis 管道(Pipeline)原理及应用
核心知识点
Redis 管道是一种优化网络传输的机制,允许客户端一次性发送多个命令,Redis 一次性返回所有命令的响应,减少网络往返次数,提升批量操作的性能。
原理详解
(1)管道的核心问题:网络往返开销
Redis 是客户端-服务器模型,默认情况下,客户端发送一个命令,需要等待 Redis 返回响应后,再发送下一个命令(同步阻塞);若执行大量批量命令(如 1000 次 set 命令),会产生 1000 次网络往返,网络延迟会严重影响性能(尤其是客户端与 Redis 不在同一机房时)。
示例:无管道时,1000 次 set 命令的流程:
客户端 → set key1 value1 → 等待 Redis 响应 → 客户端 → set key2 value2 → 等待响应 → ...(重复 1000 次)
总耗时 = 1000 ×(网络延迟 + 命令执行时间),其中网络延迟占比极高。
(2)管道的工作原理
管道的核心是「批量发送、批量响应」,客户端将多个命令打包,一次性发送给 Redis,Redis 接收所有命令后,依次执行,再将所有命令的响应结果打包,一次性返回给客户端,减少网络往返次数。
示例:有管道时,1000 次 set 命令的流程:
客户端 → 打包 1000 次 set 命令 → 一次性发送给 Redis → Redis 依次执行 1000 次命令 → 打包 1000 次响应 → 一次性返回给客户端
总耗时 = 1 × 网络延迟 + 1000 × 命令执行时间,大幅降低网络延迟的影响。
(3)管道的使用注意事项
-
管道中的命令是批量执行的,但不保证原子性:若其中一条命令执行失败,其他命令仍会继续执行(与 Redis 事务不同);
-
避免一次性发送过多命令:若打包的命令过多(如 10000 条),会占用大量内存(客户端打包命令、Redis 缓存响应结果),可能导致客户端或 Redis 内存溢出;建议分批次发送(如每批次 1000 条);
-
管道适合批量读写操作:如批量插入数据、批量查询数据,不适合需要依赖前一条命令结果的场景(如用前一条 get 命令的结果作为后一条 set 命令的参数);
-
管道与事务的结合:可在管道中加入 MULTI/EXEC 命令,实现批量命令的原子性(但会增加 Redis 的执行压力)。
(4)延伸补充:管道与批量命令(如 mset、mget)的区别
-
批量命令(mset、mget):是 Redis 原生支持的命令,可一次性操作多个键,底层是单条命令,原子性执行,网络往返次数为 1;
-
管道:可打包任意多个命令(包括不同类型的命令,如 set、hset、incr),网络往返次数为 1,但不保证原子性;
适用场景:若需批量执行同类型命令(如批量设置 String 类型),优先用 mset;若需批量执行不同类型命令,用管道。
4. Redis 发布订阅(Pub/Sub)原理及应用场景
核心知识点
Redis 发布订阅是一种消息通信模式,支持「一对多」的消息广播,客户端可分为发布者(Publisher)和订阅者(Subscriber),发布者发送消息到指定频道(Channel),所有订阅该频道的订阅者都会收到消息。
原理详解
(1)核心组件与流程
- 核心组件:
-
频道(Channel):消息的载体,发布者向频道发送消息,订阅者订阅频道接收消息(频道可动态创建,无需提前创建);
-
发布者(Publisher):发送消息的客户端,通过 PUBLISH 命令向指定频道发送消息;
-
订阅者(Subscriber):接收消息的客户端,通过 SUBSCRIBE 命令订阅一个或多个频道,通过 UNSUBSCRIBE 命令取消订阅。
-
核心流程:
-
订阅者 A、B 执行 SUBSCRIBE channel1,订阅频道 channel1;
-
发布者 C 执行 PUBLISH channel1 "hello",向 channel1 发送消息;
-
Redis 收到消息后,遍历所有订阅 channel1 的订阅者,将消息“hello”发送给 A 和 B;
-
订阅者 A、B 收到消息后,返回响应(包含频道名、消息内容)。
(2)底层实现原理
Redis 底层维护一个「频道-订阅者」的映射表(字典结构),key 是频道名,value 是订阅该频道的所有客户端列表:
-
当订阅者执行 SUBSCRIBE 命令时,Redis 会将该客户端添加到对应频道的订阅者列表中;
-
当发布者执行 PUBLISH 命令时,Redis 会根据频道名,找到对应的订阅者列表,遍历列表,将消息发送给每个订阅者;
-
当订阅者执行 UNSUBSCRIBE 命令时,Redis 会将该客户端从对应频道的订阅者列表中移除;若列表为空,会删除该频道的映射(释放内存)。
(3)发布订阅的特性与局限
特性:
-
一对多通信:一个发布者发送的消息,可被多个订阅者接收,适合广播场景;
-
无持久化:消息发送后,若订阅者未在线(未订阅或断开连接),则无法收到消息(Redis 不存储消息);
-
轻量级:无需复杂配置,仅通过简单命令即可实现发布订阅,适合简单消息通信。
局限:
-
无消息持久化:消息一旦发送,未被订阅者接收则丢失,不适合需要消息可靠传递的场景;
-
无消息确认机制:发布者发送消息后,无法确认订阅者是否收到消息;
-
无负载均衡:所有订阅者都会收到消息,无法实现消息分发(如轮询分发);
-
集群支持有限:Redis 集群中,发布者发送的消息仅会被同一节点上的订阅者接收,跨节点的订阅者无法收到消息(需使用 Redis Stream 替代)。
(4)应用场景与替代方案
适用场景:简单的广播场景,如系统通知、日志推送、实时消息提醒(无需保证消息不丢失);
替代方案:若需要消息持久化、可靠传递、负载均衡,建议使用 Redis Stream(Redis 5.0+ 新增,专为消息队列设计)或专业的消息队列(如 RabbitMQ、Kafka)。