CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Redis 缓存入门/课时/Pub/Sub 入门
RawBlame
课时目录12 节

Redis 缓存入门

  1. 01Redis 是什么
  2. 02CLI 与键命名
  3. 03String 与计数
  4. 04Hash / List / Set / ZSet
  5. 05过期与淘汰
  6. 06缓存模式
  7. 07Spring Data Redis
  8. 08会话与 Sa-Token
  9. 09Docker 运行 Redis
  10. 10Pub/Sub 入门
  11. 11测试与监控
  12. 12综合练习

Pub/Sub 入门

PUBLISH/SUBSCRIBE、与消息队列区别。

25 分钟

发布订阅:跨实例的消息广播

这一课你将能做到

  1. 用两个终端完成 SUBSCRIBE/PUBLISH 全流程,读懂 PUBLISH 返回的「订阅者数量」意味着什么
  2. 说清 Pub/Sub「不持久、不补投、无 ACK」的三大限制,判断什么场景能用、什么场景必须上 MQ
  3. 用 Spring 的 RedisMessageListenerContainer 搭一个跨实例「缓存失效广播」骨架,对应本站 Caffeine 多实例不同步问题

为什么要学

第 6 课留了一个尾巴:多实例各自持有 Caffeine 本地缓存,A 实例更新数据清了自己的缓存,B 实例还拿着最长 30 秒旧数据。如果要求更快一致,就需要A 实例更新后立刻通知所有实例清缓存——广播。

Redis 的 Pub/Sub 就是现成的广播电台:发布者喊一嗓子,所有订阅该频道的实例同时收到。它是 Redis 里最轻的「消息」能力——但轻是有代价的:不落盘、离线不补。这一课教你用对它,也教你什么时候不能用。

先建立图像

              ┌─ 实例A(SUBSCRIBE coderoadmap:cache-evict)
PUBLISH ──────┼─ 实例B(SUBSCRIBE coderoadmap:cache-evict)
(fire)      └─ 实例C(SUBSCRIBE coderoadmap:cache-evict)

频道 = 广播电台频率;订阅 = 开着收音机
特点:没人开机时喊了也白喊(消息即焚);收音机只收「开机后」的广播

跟着做

步骤 1:双终端体验收发

终端 1(订阅者):

docker exec -it redis-dev redis-cli -a devpass123
SUBSCRIBE demo:chat
# 进入长驻等待,收到消息会打印 message 频道 内容

终端 2(发布者):

docker exec redis-dev redis-cli -a devpass123
PUBLISH demo:chat "hello-subscribers"
# (integer) 1   ← 返回值:这个频道当前有 1 个订阅者收到了

切回终端 1:已实时收到。PUBLISH 返回值就是「送达数」——0 表示没人订阅,消息已丢且不会有任何补偿。

步骤 2:PSUBSCRIBE 通配订阅

终端 1 重新订阅:

PSUBSCRIBE coderoadmap:cache-evict:*

终端 2:

PUBLISH coderoadmap:cache-evict:tracks "coderoadmap:tracks:list"
PUBLISH coderoadmap:cache-evict:articles "coderoadmap:articles:list"

通配符让一个监听器接收一类频道的消息——按缓存分组广播,语义清晰。

步骤 3:亲手验证「离线即丢」

# 确保所有订阅终端都 UNSUBSCRIBE 后:
PUBLISH demo:chat "nobody-listening"
# (integer) 0   ← 0 个订阅者收到
# 现在再开一个新的订阅终端 → 永远收不到刚才那条

步骤 4:Spring 侧监听骨架

第 6 课的场景落到代码(骨架,理解三件套结构):

@Configuration
public class CacheEvictPubSubConfig {

    @Bean
    RedisMessageListenerContainer cacheEvictContainer(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener((Message message, byte[] pattern) -> {
            String body = new String(message.getBody(), StandardCharsets.UTF_8);
            // body = 要失效的 key,如 "coderoadmap:tracks:list"
            // 按前缀清对应 Caffeine cache:cacheManager.getCache("tracks").clear()
        }, new PatternTopic("coderoadmap:cache-evict:*"));
        return container;
    }
}

三件套:RedisMessageListenerContainer(容器,管线程与断线重连)+ MessageListener(回调)+ ChannelTopic/PatternTopic(频道)。

步骤 5:发布侧一行代码

// 写路径清完自己的缓存后,广播给所有实例
stringRedisTemplate.convertAndSend(
    "coderoadmap:cache-evict:tracks", "coderoadmap:tracks:list");

其他实例收到后清对应 Caffeine cache——30 秒不一致窗口被压缩到毫秒级。

讲清楚要点

  1. Pub/Sub 是即发即失(fire-and-forget):消息不在 Redis 存档,订阅者不在线就收不到;发布者只能从返回值(送达数)发现,无法补投。
  2. 它解决的是「通知」不是「传递」:通知丢了的兜底是各层 TTL(Caffeine 30s、Redis 5min)——广播失败最坏回到第 6 课的最终一致,不会出错数据。这就是它能用于缓存失效通知的根本原因。
  3. PUBLISH 返回订阅者数:生产可以借此打点「广播送达率」,发现订阅端掉线。
  4. Spring 侧三件套各司其职:容器管生命周期与重连(断线后自动重新订阅),监听器管业务回调,Topic 管匹配。
  5. 选型标尺:通知类、可丢失、低频 → Pub/Sub;订单类、不可丢、要堆积重试 → MQ(RabbitMQ/Kafka)或 Redis Stream(有持久化的流,支持消费确认)。

你可能会懵的地方

  • Pub/Sub 和 List 队列(第 4 课)区别? List 是点对点消费(一条消息被一个消费者拿走);Pub/Sub 是广播(每个订阅者都收到一份)。一个是排队,一个是喊话。

  • 消息丢失到底多严重? 看场景:缓存失效通知丢了 → TTL 兜底,无感;支付结果通知丢了 → 业务事故。前者能用 Pub/Sub,后者换工具。

  • Redis 重启订阅关系还在吗? 订阅关系由客户端持有,断线重连后 Spring 容器会自动重新订阅;但断线期间的消息已经没了。

  • 为什么广播 clear 而不是 evict 具体 key? 简单可靠:Caffeine 容量上限 200 条,clear 成本可忽略;精确失效收益小、复杂度高。

  • 本站现在用了吗? 没有——30 秒 TTL 已满足需求。这是「架构演进」素材:先有 TTL 兜底,规模大了再加广播,工具尺寸匹配问题规模。

错法对照

错法问题正解
用 Pub/Sub 传订单/支付消息离线即丢无补偿MQ 或 Redis Stream
默认 PUBLISH 就算送达返回 0 也算"发布成功"检查送达数 + TTL 兜底
频道名随意起多业务互相串台coderoadmap:cache-evict:* 分层命名
广播里塞大 payload广播放大 N 倍流量只传 key/事件名,收方自取
收到消息直接查库订阅回调阻塞快速清缓存/入队,重活异步做

卡点急救

  1. PUBLISH 返回 0:没订阅者在线。先起订阅端再测。
  2. 订阅终端卡住不动:SUBSCRIBE 是长驻阻塞模式,正常;Ctrl+C 退出。
  3. Spring 监听器没触发:确认容器 Bean 生效(Redis 开关开着)、频道通配一致、序列化方式与发布侧匹配(String body)。
  4. 收到的消息是字节数组乱码:按发布时的序列化方式解码,统一 UTF-8 String。
  5. 重连后想补历史消息:Pub/Sub 无历史,想要重放请用 Redis Stream。

面试常问

  • Redis Pub/Sub 的缺点? 不持久、离线不补、无 ACK。适合通知类可丢消息;重要消息用 MQ 或 Redis Stream。

  • 怎么实现多实例本地缓存同步失效? 更新实例清自身缓存后 PUBLISH 失效事件,各实例的 RedisMessageListenerContainer 收到后 clear 对应 Caffeine cache;TTL 兜底保证广播丢失也不出脏数据。

  • Pub/Sub、List、Stream 选型? 广播可丢 → Pub/Sub;点对点轻量任务 → List/BRPOP;点对点要持久/ACK/重放 → Stream 或专业 MQ。

自测三问

  1. PUBLISH 返回 (integer) 0 意味着什么?消息去哪了?
  2. 本站缓存失效广播里,广播丢失的最坏后果是什么?靠什么兜底?
  3. RedisMessageListenerContainer 三件套是哪三个角色?各自负责什么?

小结一句话

Pub/Sub 是「即发即失」的跨实例广播:适合缓存失效通知这类丢了也有 TTL 兜底的事件,不可丢的消息请上 MQ 或 Stream。

延伸

  • Redis Pub/Sub 官方文档
  • 下一课:监控与测试——INFO 看健康、SLOWLOG 抓慢命令、Testcontainers 写真 Redis 的测试。
加载本节练习…
← 上一课Docker 运行 Redis下一课 →测试与监控