Redis设计与实现-发布与订阅

162 阅读7分钟

发布与订阅

频道的订阅和退订

客户端可以通过 SUBSCRIBE 命令订阅某个或某些频道(建立订阅关系),Redis将所有频道的订阅关系都保存在服务器状态的 pubsub_channels 字典里,这个字典键时某个被订阅的频道,值是一个链表,链表记录了所有订阅这个频道的客户端:

struct redisServer{
    // ...
    // 保存所有频道的订阅关系
    dict *pubsub_channels;
    // ...
};

订阅频道

当客户端执行 SUBSCRIBE 命令订阅某个或某些频道时,服务器都会将客户端与被订阅的频道在 pubsub_channels 字典中进行关联。

根据频道是否已经有其它订阅者,关联操作分两种情况:

  • 有,那就将客户端添加到订阅者链表的末尾

  • 没有,先在 pubsub_channels 字典中未频道创建一个键,并将这个键的值设置为空链表,然后将客户端添加到链表。 SUBSCRIBE 命令的实现伪代码如下:

def subscribe(*all_input_channels):
	# 遍历输入的所有频道
	for channel in all_input_channels:
		# 如果channel不存在于 pubsub_channels 字典中(没有任何订阅者)
		# 那么在字典里添加channel键,并设置它的值为空链表
		if channel not in server.pubsub_channels:
			server.pubsub_channels[channel] = []
		# 将订阅者添加到频道所对应的链表的末尾
		server.pubsub_channels[channel].append(client)

退订频道

UBSUBSCRIBE 命令的行为就是当一个客户端退订某个或某些频道的时候,服务器将从 pubsub_channels 中解除客户端与被退订频道之间的关联:

  • 程序会根据被退订频道的名字,在 pubsub_channels 字典中找到频道对应的订阅者链表,然后从链表中删除退订客户端的信息

  • 如果删除退订客户端之后,频道的订阅者链表为空链表,那么说明这个频道没有任何订阅者,程序将从 pubsub_channels 字典中删除频道对应的键

UNSUBCRIBE 命令的伪代码:

def unsubscribe(*all_input_channels):
	#  遍历要退订的所有频道
	for channel in all_input_channels:
		# 在订阅者链表中删除退订的客户端
		server.pubsub_channels[channel].remove(client)
       	 # 如果频道已经没有任何订阅者了(订阅者链表为空)
         # 那么将频道从字典中删除
         if len(server.pubsub_channels[channel]) == 0:
			server.pubsub_channels.remove(channel)

模式的订阅与退订

服务器将所有模式的订阅关系保存在服务器状态的 pubsub_patterns 属性里:

struct redisServer{
    // ...
    // 保存所有模式订阅关系
    list *pubsub-patterns;
    // ...
};

订阅模式

当客户端执行 PSUBSCRIBE 命令订阅某个或某些模式时,服务器会对每个被订阅的模式执行下面两个操作:

新建一个 pubsubPattern 结构,将结构的 pattern 属性设置为被订阅的模式, client 属性设置为订阅模式的客户端 将pubsubPattern 结构添加到 pubsub_patterns 链表的表尾 PSUBSCRIBE 命令的实现伪代码:

def psubscribe(*all_input_patterns):
	# 遍历输入的所有模式
	for pattern in all_input_patterns:
		# 创建新的 pubsubPattern 结构
		# 记录被订阅的模式,以及订阅模式的客户端
		pubsubPattern = create_new_pubsubPattern()
         pubsubPattern.client = client
         pubsubPattern.pattern = pattern
         
         # 将新的 pubsubPattern 追加到 pubsub_patterns 链表末尾
         server.pubsub_patterns.append(pubsubPattern)

17.2.2. 退订模式 当客户端退订某个或某些命令时,服务器将在 pubsub_patterns 链表中查找并删除那些 pattern 属性为被退订模式,并且 client 属性为之心退订命令的客户端的 pubsubPattern 结构。

PUNSUBSCRIBE 命令的实现伪代码:

def punsubscribe(*all_input_patterns)
 
    # 遍历所有要退订的模式
    for pattern in all_input_patterns;
 
        # 遍历subpub_patterns链表中的所有pubsubPattern结构
        for pubsubPattern in server.pubsub_patterns:
 
        # 如果当前客户端与subpubPattern记录的客户端相同
        # 并且退订的模式也和pubsubPattern记录的模式相同
        if client == pubsubPattern.clent and pattern = pubsubPattern.pattern
 
            # 将这个pubsubPattern删除
            server.pubsub_patterns.remove(pubsubPattern)

发送消息

当一个Redis客户端执行 PUBLISH 命令将消息 message 发送给频道 channel 时,服务器执行两个动作:

  1. 将消息 message 发送给 channel 频道的所有订阅者。

  2. 如果有一个或多个模式 pattern 与频道 channel 相匹配,那么将消息 message 发送给 pattern 模式的订阅者。

将消息发送给频道订阅者

上面提到过,服务器状态中的 pubsub_channels 字典记录了所有频道的订阅关系,所有为了将消息发送给 channel 频道的所有订阅者,PUBLISH 命令要做的就是在这个字典里找到频道 channel 的订阅者名单(链表),然后将消息发送给名单上的所有客户端。

PUBLISH 命令将消息发送给频道订阅者的方法的伪代码:

def channel_publish(channel,message)
 
    # 如果channel模式不存在于pubsub_channels字典中,直接返回
    # 说明channel频道没有任何订阅者
    if channel not in server.pubsub_channels
        return
 
    # 遍历channel频道的订阅者链表,将消息发送给链表中的每一个订阅者
    for subscriber in server.pubsub_channels[channel]
        send_message(subscriber,message)

将消息发送给模式订阅者

上面提到过 pubsub_patterns 链表记录了所有模式的订阅关系,所以为了将消息发送给所有与channel频道相匹配的模式的订阅者,PUBLISH 命令要做的就是遍历 punsub_patterns 链表,查找那些与channel频道相匹配的模式,并将消息发送给订阅了这些模式的客户端。

PUBLISH 命令将消息发送给模式订阅者的方法的伪代码:

def pattern_publish(channel,message)
 
    #遍历所有模式订阅消息
    for pubsubpattern in server.pubsub_patterns
 
        #如果频道和模式相匹配
        if match(channel,pubsubPattern.pattern):
 
            #那么将消息发送给该模式的客户端
            send_message(pubsubPattern.client,message)

最后 PUBLISH 命令的实现的伪代码如下:

def publish(channel,message):
    # 将消息发送给channel频道的所有订阅者
    channel_publish(channel,message)
    # 将消息发送给所有和channel频道相匹配的模式的订阅者
    pattern_publish(channel,message)

查看订阅信息

PUBSUB CHANNELS

PUBSUB CHANNELS [pattern] 子命令用于返回服务器当前被订阅的频道,其中pattern参数是可选的:

  • 如果不给定pattern参数,那么命令返回服务器当前被订阅的所有频道;

  • 如果给定pattern参数,那么命令返回服务器当前被订阅的频道中与pattern模式相匹配的频道。 这个子命令是通过遍历服务器 pubsub_channels 字典的所有键(每个键都是一个被订阅的频道),然后记录并返回所有符合条件的频道来实现,伪代码如下:

def pubsub_channels(pattern=None):
	# 一个列表,用于记录所有符合条件的频道
	channel_list = []
	# 遍历服务器中的所有频道
	# (也即是 pubsub_channels 字典的所有键)
	for channel in server.pubsub_channels:
		# 当以下两个条件的任意一个满足时,将频道添加到链表里
		# 1) 用户没有指定pattern参数
		# 2) 用户指定了pattern参数,并且channel和pattern匹配
		if  (pattern is None) or match(channel,pattern):
			channel_list.append(channel)
	# 向客户端返回频道列表
    return channel_list

PUBSUB NUMSUB

PUBSUB NUMSUB [channel-1 channel-2 …… channel-n]子命令接受任意多个频道作为输入参数,并返回这些频道的订阅者数量。

这个子命令是通过在 pubsub_channels 字典中找到频道对应的订阅者链表,然后返回所有订阅者链表的长度来实现(长度即频道订阅者的数量),伪代码如下:

def pubsub_numsub(*all_input_channels):
	# 遍历输入的所有频道
	for channel all_input_channels:
		# 如果 pubsub_channels 字典中没有channel这个键
		# 那么说明channel频道没有任何订阅者
		if (channel not in server.pubsub_channels)
            # 返回频道名
            reply_channel_name(channel)
            # 订阅者数量为0
            reply_subscribe_count(0)
         # 如果pubsub_channels 字典中存在channel键
         # 那么说明channel频道至少有一个订阅者
         else :
			# 返回频道名
			reply_channel_name(channel)
             # 订阅者链表的长度就是订阅者数量
             reply_subscribe_count(len(server.pubsub_channels[channel])

PUBSUB NUMPAT

PUBSUB NUMPAT子命令用于返回服务器当前被订阅模式的数量。

这个子命令时通过返回 pubsub_patterns 链表的长度来实现的(长度即服务器被订阅的数量,伪代码如下:

def pubsub_numpat():
	# pubsub_patterns 链表的长度就是被订阅模式的数量
	reply_pattern_count(len(server.pubsub_patterns0))

总结

发布与订阅

1.  服务器状态在pubsub_channels字典保存了所有频道的订阅关系:SUBSCRIBE命令负责将客户端和被订阅的频道关联到这个字典里面,而UNSUBSCRIBE命令则负责解除客户端和被退订频道之间的关联。

2. 服务器状态在pubsub_patterns链表保存了所有模式的订阅关系: PSUBSCRIBLE命令负责将客户端和被订阅的模式记录到这个链表中,而PUNSBUSCRIBLE命令则负责移除客户端和被退订模式在链表中的记录。

3.  PUBLISH命令通过访问pubsub_channels字典来向频道的所有订阅者发送消息,通过访问pubsub_patterns链表来向所有匹配频道的模式的订阅者发消息。

4.  PUBSUB命令的三个子命令都是通过读取pubsub_channels字典和pubsub_patterns链表中的信息来实现的。