什么是redis的发布/订阅模式
Redis中的订阅、发布实现了发布/订阅消息范式,发布者不是计划发送消息给特定的订阅者,而是发布消息到不同的频道,发布者不需要知道是哪些订阅者订阅了消息。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道是什么样的发布者发布的消息。这种发布者和订阅者的解耦合可以带来更大的扩展性和更加动态的网络拓扑。
–来自官方的原文翻译。
官方文档地址
Redis的发布订阅模式中有三部分组成:
- Publisher(发布者):发送消息到频道中,每次只能往一个频道发送一条消息;
- Subscriber(订阅者):订阅频道,订阅者可以同时订阅多个频道;
- Channel(频道):将发布者发布的消息转发给当前订阅此频道的订阅者;
模型图如上图所示。
使用命令
-
发布消息到指定频道:
#发布消息到指定的频道 PUBLISH channel message 127.0.0.1:6379> publish test 'hello' (integer) 1 // 1代表有一个订阅者
-
订阅者订阅消息
#订阅给定的一个或多个频道的信息 SUBSCRIBE channel [channel ...] 127.0.0.1:6379> subscribe test Reading messages... (press Ctrl-C to quit)
订阅者接收到消息:
1) "message" 2) "test" 3) "hello"
- 其他命令
#订阅一个或多个符合给定模式的频道
PSUBSCRIBE pattern [pattern ...]
#指退订给定的频道
UNSUBSCRIBE [channel [channel ...]]
#退订所有给定模式的频道
PUNSUBSCRIBE [pattern [pattern ...]]
#查看订阅与发布系统状态
PUBSUB subcommand [argument [argument ...]]
查看所有有订阅者的频道
pubsub numpat #查看模糊订阅的数量
pubsub numsub [channel…] #列出给定频道的订阅者数量
PUB/SUB原理
我们从图中可以看到每个频道放入字典数组中,对应频道的订阅者则放入链表中,当我们发送一个publish命令时,首先字典数组遍历找到对应的频道,然后找到对应的订阅链,依次发送消息。
所以我们可以看出每次发送消息时我们的都需要遍历这个字典,也就是说它的执行时间效率为O(n),但是我们redis的宗旨是快,减少执行O(n)的命令,这违背了我们当初的初衷。
对比消息中间件
从上面的案例可以看出,redis的发布订阅模式很像消息队列,但是也有许多不同。
-
Redis的发布订阅以及List并不是要和专业的消息队列对标,而是可以实现类似的功能,真正在消息队列领域做的好的有很多,RabbitMQ,ActiveMQ,RocketMQ,Kafka等等,发布订阅相比于它们有什么异同呢?
不同点:- 持久化:Kafka会将数据持久化到磁盘内,而Redis的发布订阅做不到;
断点消费:上面也提到,当订阅者断开重连会丢失断开期间发布者发布的消息,而kafka中会记录每个消费者消费的topic的offset,因此kafka可以从断开的offset继续消费; - 偏移量:基于上一条,同样的kafka的消费者可以指定从某个offset开始重新消费,而Redis发布订阅根本不会记录订阅者消费的偏移量;
- 消费方式:在Redis发布订阅中,数据消费情况是由发布者控制的,当发布者发布到频道中后,只有当前连接了频道的订阅者才能消费到数据,断开重连的会失去那部分数据。而kafka中消费进度是由消费者控制的,消费者从topic中拉取数据并记录消费的offset。
相同点:
- 消息模型:在JMS消息模型中有点对点和订阅发布两种,Kafka和Redis发布订阅都是采用发布订阅的模型。
- 消费者组:Kafka里在不同的消费者组中的消费者消费相同的topic时会各自维护一个offset,因此不会出现A消费之后的数据,B就消费不到的情况。Redis中订阅者订阅相同的频道也不会出现类似的情况。
- 持久化:Kafka会将数据持久化到磁盘内,而Redis的发布订阅做不到;
-
总结
- 当挂掉的订阅者重新连上的时候,在断连期间发布者发送的消息,对于这个发布者来说就是彻底丢失了。
- 如果Redis停机重启,PubSub的消息是不会持久化的,毕竟Redis开机就相当于一个订阅者都没有,所有的消息会被直接丢弃。
- 难以保证消息队列的ACK,消息发送出去后没有一个回馈过程,消息无法做持久化。
- 如果保证消息持久化,那么必定损失性能,首先我们需要把消息存入磁盘,然后从磁盘中读取数据到内存去操作,这个过程是非常耗时的。
- PubSub执行效率低,执行效率是O(n),违背了redis设计初衷。