3

Redis的发布订阅功能由 PUBLISHSUBSCRIBEPSUBSCRIBE等命令组成。

通过执行SUBSCRIBE命令,客户端可以订阅一个或多个频道从而成为这些频道的订阅者(subscriber),
当有其它客户端向被订阅的频道发送消息时,频道的所有订阅者都能收到这条消息。

在下图中客户端A、客户端B、客户端C,分别订阅了cctv.5这个频道。这个时候某个客户端执行了如下这条命令:

PUBLICH "cctv.5" "中国男篮战胜了美国男篮,获得了奥运冠军"

此时订阅了cctv.5这个频道的所有客户端都将收到这条消息。

图片描述
除了订阅频道之外,客户端还可以通过执行PSUBSCRIBE命令订阅一个或多个模式,从而成为这些模式的订阅者。每当有其他客户端向某个频道发送消息时,消息不仅仅会发送给这个频道的订阅者,还会发送给与这个频道相匹配的模式订阅者。

图片描述
上图中,我们给cctv.3这个频道发送了一条消息"同一首歌",该消息首先会发送给它的订阅者client A,然后根据模式匹配,cctv.3符合cctv.*这个模式。所以订阅了cctv.*频道的订阅者也都可以收到消息呢。(●´∀`●)ノ

频道的订阅

当一个客户端执行SUBSCRIBE命令订阅某个或某些频道的时候,这个客户端与被订阅频道之间就建立了一种订阅关系。

Redis 将所有频道的订阅关系都保存在服务器状态的pubsub_channels字典里面,这个字典的键是某个被订阅的频道,值是一个链表,链表里面记录了所有订阅这个频道的客户端。

图片描述

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

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

  1. 频道已经有其他订阅者,那么在pubsub_channels字典中必然有相应的订阅者链表。程序唯一要做的是将客户端添加到链表的末尾。

  2. 频道没有其它订阅者,程序首先要在pubsub_channels字典中为频道创建一个键,并将值设置为空链表,然后将客户端添加到链表。成为链表的第一个元素。

php 发布与订阅demo


// publish.php
<?php
    $Redis = new Redis;
    $Redis->connect('127.0.0.1',6379);
    // 频道名
    $channel = 'cctv.5';
    $message = '中国男篮战胜了美国男篮,获得了奥运冠军';
    $Redis->publish($channel,$message);
?>

// subscribe.php
<?php
    ini_set('default_socket_timeout', -1);
    $Redis = new Redis;
    $Redis->connect('127.0.0.1',6379);
    $channel = ['cctv.5'];    // 订阅多个频道则直接添加该数组子元素
    $Redis->subscribe($channel,function($instance,$channel,$message){
        // 这里除了SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE这4条命令之外其它命令都不能使用
        var_dump($instance,$channel,$message);
    });
?>

留意我们接收消息的回调函数。除了SUBSCRIBEPSUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBE这4条命令以外,其它命令都不能使用。

如果你使用的是CLI(命令行界面)此处是有bug的,你可能无法退订消息。只能通过组合键Ctrl+c结束退订。
图片描述

退订频道

使用UNSUBSCRIBE命令可以退订某个或某些频道。服务器将从pubsub_channels中解除客户端与被退订频道之间的关联:

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

  2. 如果删除了客户端之后,频道的订阅者链表变成了空链表,那么说明这个频道已经没有任何的订阅者了,程序将从pubsub_channels字典中删除频道对应的键。

订阅模式

服务器将所有的订阅关系都保存在服务器状态的pubsub_channels属性里面,在模式订阅里服务器也将所有模式的订阅关系都保存在服务器状态的pubsub_patterns属性里面。

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

pubsub_patterns 属性是一个链表,链表中的每个节点都包含着一个pubsubPattern结构,这个结构的pattern属性记录了被订阅的模式,而client属性则记录了订阅模式的客户端。

typedef struct pubsubPattern {
    // 订阅模式的客户端
    redisClient *client;
    // 被订阅的模式
    robj * pattern;
} pubsubPattern;

图片描述

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

  1. 新建一个pubsubPattern结构,将结构的pattern属性设置为被订阅的模式(如:music.*),client 属性设置为订阅模式的客户端。

  2. pubsubPattern结构添加到pubsub_patterns链表的表尾。

退订模式

模式的退订命令PUNSUBSCRIBEPSUBSCRIBE命令的反操作。当一个客户端退订某个或某些模式的时候,服务器将在pubsub_patterns链表中查找并删除那些pattern属性为退订模式并且client属性为执行退订命令的客户端的pubsubPattern结构。

发送消息

redis客户端执行 PUBLISH <channel> <message>命令将消息发送给频道的时候,服务器需要执行以下两个操作:

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

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

第一个操作的伪代码:


def channel_publish(channel,message):
/*
    如果channel键不存在pubsub_channels字典中
    那么说明channel频道没有任何订阅者
    程序不做发送动作直接返回
*/
if channel not in server.pubsub_channels:
    return
    
/*
    运行到这里,说明channel频道至少有一个订阅者
    程序将遍历channel频道的订阅者列表
    将消息发送给所有订阅者
*/
for subscriber in server.pubsub_channels[channel]:
    send_message(subscriber,message)
    

第二个操作,因为服务器状态中的pubsub_patterns链表记录了所有模式的订阅关系,所以为了将消息发送给所有与channel频道相匹配的模式的订阅者,PUBLISH命令要做的就是遍历整个pubsub_patterns链表,查找那些模式相匹配的订阅者。


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

最后PUBLISH命令可以用一下伪代码来描述:

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

那页阳光
295 声望16 粉丝

低调的很闷骚,沉默的很高傲。