Redis的订阅和发布

发布与订阅模型,放在每一位开发者身上,都是耳熟能详的一对词语。与设计模式里面的--观察者模式非常相似。在微服务中,把消息中介分离出来成为独立服务,无论是跨服务通信开发和运维角度,redis的发布订阅模式似乎是很好的选择。

我们先从原理和使用实例出发来理解一下redis的这个发布与订阅是怎么运行的,再结合开发场景说说它的优势和劣势。

首先我们理解一点,发布者可以做1:N的广播,反过来说,多个订阅者可以同时订阅一个发布者的消息。

结构图大致如此:

 

背后原理:

一、精确订阅

Redis服务器中维护着一个pubsub_channels字典(可以类似理解为map或者键值对),所有的频道和订阅关系都存储在这里。字典的键为频道的名称,而值为订阅频道的客户端连接链表。

我们可以将操作行为分为两类,订阅者行为和发布者行为。

订阅者,订阅频道过程:

当有新的客户端订阅某个频道时,会发生两种情况中的一种:

1)如果频道已经存在,则新的客户端会添加到pubsub_channels对应频道的链表末尾

2)如果频道原本不存在,则会为频道创建一个键,该客户端成为链表的第一个元素

 

订阅者,退订频道过程:

当一个客户端退订一个频道的时候,pubsub_channels对应键的链表会删除该客户端

 

发布者,发送信息过程:

服务器会遍历pubsub_channels中对应键的链表,向每一个客户端发送信息

这也是为什么发布者能实现1:N广播的原因了。

 

二、模糊订阅

服务器维护着一个pubsub_patterns链表,链表的pattern属性记录了被订阅的模式,而client属性记录了订阅模式的客户端信息

订阅者,订阅模式过程:

当有新的客户端订阅某个模式的时,会进行如下步骤:

1)创建一个链表节点,pattern属性记录订阅的模式,client记录订阅模式的客户端

2)将这个链表节点添加到pubsub_patterns链表中

 

订阅者,退订模式过程:

当一个客户端退订某一个模式的时候,服务器遍历pubsub_patterns找到对应的pattern,再往下找到对应该client客户端的节点,将该节点删除

 

发布者,发送信息过程:

服务器遍历pubsub_channels,查找与channels频道相匹配的模式麻将消息发送给订阅了这些模式的客户端。

 

Redis订阅系统的优势:

1. 当一个客户端向频道发送一个信息,订阅了同一个频道/模式的多个客户端可以同时接收到信息,类似广播的机制。

2. 便于Sentinel哨兵与服务器间的通信并进行监控

 

 实例操作

1. 订阅频道

命令格式:

SUSCRIBE   channel1  [channel2]  [channel3]

例如(同时订阅两个频道):

redis> subscribe  local_system  remote_system     # 订阅者同时订阅两个频道

 

结果
Reading messages... (press Ctrl-C to quit)

1) "subscribe"                 # 返回值的类型:显示订阅成功

2) "local_system"              # 订阅的频道名字

3)"remote _system "

4) (integer) 2                # 目前已订阅的频道数量

 

发布者发布信息:

redis> publish local_system  hello
(integer) 1    # 消息发送成功

注意:客户端一点与某个频道建立连接就不能进行其他操作,一旦客户端退出连接,订阅的频道自动会断开。

 

2. 订阅模式

PSUBSCRIBE  pattern1  [pattern2]  [pattern3]

pattern可以用符号*进行模糊匹配,例如:

remo*   #匹配以remo开头的模式

*system  #匹配以system结尾的模式

 

操作案例:

订阅者,模糊订阅频道

 

发布者,发布信息:

 

订阅者,获取到信息:

 

 

3. 取消订阅:

命令:

1)取消订阅频道

UNSUBSCRIBE  channel1  [channel2]

2)取消订阅模式

PUNSUBSCRIBE  [pattern [pattern …]]

 

4. 发布消息

命令:

PUBLISH channel message

向一个频道发送信息,所有订阅这个频道的客户端都会接收到信息

 

其他辅助命令

查看已有的频道

命令:

PUBSUB  subcommand [argument [argument ...]]

subcommand子命令:

1)PUBSUB CHANNELS  [pattern]

列出当前的活跃频道

如果给定pattern参数,则会返回服务器当前被订阅的频道中与pattern模式相匹配的频道

  2)PUBSUB NUMSUB [channel-1 … channel-N]


  3)PUBSUB NUMPAT

  返回订阅模式的数量

 

至此,我们大致理解了Redis的发布订阅机制是如何运行的。那么实际有什么应用场景呢?

1)点赞功能,给多个客户端更新最新的点赞数量。

由于HTTP无状态的特性,我们可以用websocket来解决通信双方保活的问题,服务端一旦接收到发布者更新的点赞数量,就可以通过websocket发送给客户端。

由此延伸开来,类似的更新与推送,都可以用Rredis + Websocket得到实现。要举的例子还可以很多很多。。。

2)作为消息队列

服务端内部服务之间的通信,可以通过Redis发布订阅机制实现消息广播

 

优劣势:

1)优势

基于内存,通信效率高。使用足够简单

2)劣势

在探讨一个中间件是否适合做消息队列的时候,核心目标就是不允许数据丢失。而消息是否会发生丢失,围绕的重点也就在于以下 3 个环节:

1.  生产者会不会丢消息?

2.  消费者会不会丢消息?

3. 消息队列中间件会不会丢消息?

问题1/3:有概率丢失。当内存撑得很大的时候,Redis是有概率会丢失数据的。

问题2:显然,一旦订阅者离线,Redis的消息就会丢失。

基于以上的分析,一个建议:

对于消息并非非常重要,丢失中间的某些消息也不影响系统正常运行和用户体验的前提下,可以使用Redis的订阅发布机制。

如果对于消息的准确率要求非常高的场景,有更好的替代方案,使用专业的消息中间件,例如RabbitMQ、Activemq、Kafka等等

 

题外话

Sentinel服务器与Master服务器/Slave服务器之间的订阅发布系统是Sentinl(哨兵)监控过程的一个重要环节,通过订阅发布系统达到监控服务器状态的作用。其运行原理与上面的客户端服务器之间的订阅机制无太大区别,都是基于网络连接的数据传输。Sentinel之间的通信也是通过Sentinel与服务器间的这个订阅发布机制实现的,一个Sentinel通过服务器的频道发送信息,其他Sentinel就会接收到。

 

posted @ 2019-02-12 01:48  2015夏  阅读(5073)  评论(0编辑  收藏  举报