写在文章开头
Redis 的发布订阅模式,正是这样一把强大的“通信钥匙”,它为开发者们开启了一扇实现高效消息交互的大门。通过发布订阅模式,一个消息生产者可以将消息发布到指定的频道中,而多个对该频道感兴趣的消息消费者能够同时接收到这些消息,实现了消息的一对多广播。
在本文中,我们将通过详细的实践演示,深入探索 Redis 发布订阅模式的奥秘。从基础概念的简要回顾,到搭建环境、编写代码示例,再到对运行结果的详细分析,一步步带你领略这一强大模式在实际项目中的应用魅力,让你在面对类似的消息通信需求时能够游刃有余。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的技术人,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
同时也非常欢迎你star我的开源项目mini-redis:https://github.com/shark-ctrl/mini-redis
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
详解redis发布订阅模式
什么是发布与订阅
redis
发布订阅是一种解耦生产者和消费者一种消息通信模式,订阅者通过订阅channel
等待最新的消息,发布者按需将消息发送到指定channel上供订阅者消费:

redis发布订阅模式的使用
redis发布订阅的两种模式
redis
发布订阅有两种方式:
基于精确频道的订阅模式,即订阅者订阅名为 channel-1
的频道,那么只有channel-1
有消息时才会通知这些订阅。基于匹配模型的订阅模式,即订阅者可以通过表达式订阅频道,例如订阅者订阅了 channel*
的频道,那么所有前缀为channel
的频道都会向这个订阅者发布消息。

基于频道的订阅模式
基于频道订阅模式的指令格式如下,可以看到订阅者可以订阅多个频道
subscribe channel [channel ...]
所以我们开启一个redis
客户端,订阅一个channel:sport
的频道,对应的指令和输出结果如下,可以看到发送指令后redis服务端返回1,通知订阅者成功订阅该频道:
# 客户端1 订阅 channel:sport
127.0.0.1:6379> SUBSCRIBE channel:sport
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:sport"
3) (integer) 1
对应的我们也给出发布消息的指令格式:
publish channel message
此时,我们再开启一个redis
客户端,发布一条消息到channel:sport
的频道:
# 另一个客户端发送消息
127.0.0.1:6379> PUBLISH channel:sport "this is why we play"
(integer) 1
127.0.0.1:6379>
此时刚刚订阅消息的redis
客户端就会实时的收到这条消息:

基于匹配模式的发布与订阅示例
有时候我们会订阅多个频道,我们不可能每次都去手动增加订阅的频道,例如我们当前订阅频道有:c1、c2、c3、c4、c5、c6、c7、c8
。将来还可能出现c9等情况。我们不可能实时去添加订阅的频道。 观察上面的频道我们发现频道都是以c开头,后续的数字不断变化,所以我们完全可以使用模式匹配来实现频道订阅。
模式订阅和取消的命令为
psubscribe pattern [pattern...]
punsubscribe [pattern [pattern ...]]
关于parttern
常见的匹配符有
1. *:表示任意占位符,例如c*,可以匹配c、c1、c111
2. ?*:匹配一个及以上个占位符
3. ?:表示匹配一个占位符
我们希望订阅c1-c9
的频道基于模式匹配
我们就能够做到这一点
我们首先开启一个客户端,使用模式匹配发起订阅
# 订阅匹配cxx相关的模式
PSUBSCRIBE c?*
然后我们在开启另一个客户端,发送消息到c1频道
127.0.0.1:6379> PUBLISH c1 "this is c1 message"
(integer) 1
127.0.0.1:6379>
刚刚订阅的客户端就会收到消息
127.0.0.1:6379> PSUBSCRIBE c?*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "c?*"
3) (integer) 1
1) "pmessage"
2) "c?*"
3) "c1"
4) "this is c1 message"
注意:当你订阅PSUBSCRIBE c?* c1
订阅时,若另一个客户端发送消息到c1你会收到两条消息(原因会在后文源码解析时补充)。
如下便是PSUBSCRIBE c?* c1
的收到PUBLISH c1 "this is c1 message"的消息内容
# 订阅c?* 和精确的c1两个频道
127.0.0.1:6379> PSUBSCRIBE c?* c1
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "c?*"
3) (integer) 1
1) "psubscribe"
2) "c1"
3) (integer) 2
1) "pmessage"
2) "c1"
3) "c1"
4) "this is c1 message"
1) "pmessage"
2) "c?*"
3) "c1"
4) "this is c1 message"
基于spring boot集成redis落地发布与订阅模式
我们将使用一个用户订阅channel:sport
,一个用户订阅channel:stock
,而另一个用户则通过channel*订阅两个都订阅:

了解了大体思路,我们就开始落地这个需求,首先第一步还是引入redis的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
然后在配置文件中给出redis文件的配置信息:
spring.redis.host=127.0.0.1
spring.redis.port=6379
随后就是定义3个订阅者的处理器的通用接口定义,后续我们将通过函数时编程来分别落地3个订阅者的处理器:
public interface RedisMsgHandler {
/**
* 处理发布者发送的消息
* @param message
*/
void handleMessage(String message);
}
然后我们就可以针对redis
不同的订阅者进行配置进行声明:
@Configuration
@EnableCaching
@Slf4j
public class RedisConfig {
/**
* Redis消息监听器容器
*
* @param connectionFactory
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//订阅体育消息
container.addMessageListener(listenerAdapter(msg -> log.info("收到体育新闻,消息内容:{}", msg)), new PatternTopic("channel:sport"));
//订阅股票消息
container.addMessageListener(listenerAdapter(msg -> log.info("收到库存消息,消息内容:{}", msg)), new PatternTopic("channel:stock"));
//channel前缀的消息都订阅
container.addMessageListener(listenerAdapter(msg -> log.info("收到channel前缀的消息:{}", msg)), new PatternTopic("channel:*"));
return container;
}
/**
* 配置消息接收处理类
*
* @return
*/
@Bean
@Scope("prototype")
MessageListenerAdapter listenerAdapter(RedisMsgHandler handler) {
//这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
//也有好几个重载方法,这边默认调用处理器的方法 叫 handleMessage 可以自己到源码里面看
return new MessageListenerAdapter(handler, "handleMessage");//注意2个通道调用的方法都要为 receiveMessage
}
}
最后我们启动一个定时器定时向频道发送消息:
@EnableScheduling
@Component
public class SenderTask {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//向redis消息队列index通道发布消息
@Scheduled(fixedRate = 2000)
public void sendMessage() {
stringRedisTemplate.convertAndSend("channel:stock", "股票涨了"+Math.random()+"个百分点");
stringRedisTemplate.convertAndSend("channel:sport", "篮球新闻 No."+((int)(Math.random()*100)+1)+"选手得分");
}
}
最后运行结果如下所示:
2025-01-11 22:39:03.129 INFO 16952 --- [ container-3] com.sharkChili.RedisConfig : 收到channel前缀的消息:股票涨了0.5562952653668436个百分点
2025-01-11 22:39:03.129 INFO 16952 --- [ container-4] com.sharkChili.RedisConfig : 收到体育新闻,消息内容:篮球新闻 No.20选手得分
2025-01-11 22:39:03.129 INFO 16952 --- [ container-5] com.sharkChili.RedisConfig : 收到channel前缀的消息:篮球新闻 No.20选手得分
2025-01-11 22:39:05.121 INFO 16952 --- [ container-6] com.sharkChili.RedisConfig : 收到库存消息,消息内容:股票涨了0.47411355078897777个百分点
2025-01-11 22:39:05.122 INFO 16952 --- [ container-7] com.sharkChili.RedisConfig : 收到channel前缀的消息:股票涨了0.47411355078897777个百分点
2025-01-11 22:39:05.135 INFO 16952 --- [ container-9] com.sharkChili.RedisConfig : 收到channel前缀的消息:篮球新闻 No.15选手得分
2025-01-11 22:39:05.135 INFO 16952 --- [ container-8] com.sharkChili.RedisConfig : 收到体育新闻,消息内容:篮球新闻 No.15选手得分
2025-01-11 22:39:07.135 INFO 16952 --- [ container-11] com.sharkChili.RedisConfig : 收到channel前缀的消息:股票涨了0.886860372468581个百分点
2025-01-11 22:39:07.135 INFO 16952 --- [ container-10] com.sharkChili.RedisConfig : 收到库存消息,消息内容:股票涨了0.886860372468581个百分点
2025-01-11 22:39:07.136 INFO 16952 --- [ container-12] com.sharkChili.RedisConfig : 收到体育新闻,消息内容:篮球新闻 No.89选手得分
2025-01-11 22:39:07.136 INFO 16952 --- [ container-13] com.sharkChili.RedisConfig : 收到channel前缀的消息:篮球新闻 No.89选手得分
发布订阅的常见的使用场景和优缺点
使用场景如:聊天室、公告牌、异步处理电商订单非核心操作等需要实现消息解耦的场景都可以使用消息订阅发布,如下所示,这就是电商下单业务对于redis
发布订阅模式的使用模型图:

当然redis发布订阅模式优缺点也很明显,我们先来说说优点,redis发布订阅模式 实现简单,对于简单的解耦生产者和消费者关系的应用场景绰绰有余,而缺点也是一样,因为redis发布订阅模式实现比较简单,并没有持久化机制无法保证可靠消费和故障恢复,同时,相较于Kafka
、RocketMQ
,Redis
的实现略显粗糙,无法实现消息堆积与回溯。
redis如何实现发布订阅模型
关于redis发布订阅模型,对于底层实现感兴趣的读者,可以参考笔者下面这篇关于pub/sub的源码解析::https://mp.weixin.qq.com/s/sQHv6DHuJGCyiJzqeDVdvQ
小结
本文从redis发布订阅模型基本介绍和几个实践案例角度带读者快速入门了一下redis发布订阅模型的使用场景以及一些注意事项,希望对你有帮助。
我是 sharkchili ,CSDN Java 领域博客专家,mini-redis的作者,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。
同时也非常欢迎你star我的开源项目mini-redis:https://github.com/shark-ctrl/mini-redis
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
参考
Redis开发与运维:https://book.douban.com/subject/26971561/
Redis进阶 - 消息传递:发布订阅模式详解:https://www.pdai.tech/md/db/nosql-redis/db-redis-x-pub-sub.html#基于频道channel的发布订阅如何实现的




