Redis的發佈與訂閱

Redis的發佈訂閱功能由 PUBLISHSUBSCRIBEPSUBSCRIBE等命令組成。php

經過執行SUBSCRIBE命令,客戶端能夠訂閱一個或多個頻道從而成爲這些頻道的訂閱者(subscriber),
當有其它客戶端向被訂閱的頻道發送消息時,頻道的全部訂閱者都能收到這條消息。redis

在下圖中客戶端A、客戶端B、客戶端C,分別訂閱了cctv.5這個頻道。這個時候某個客戶端執行了以下這條命令:數組

PUBLICH "cctv.5" "中國男籃打敗了美國男籃,得到了奧運冠軍"服務器

此時訂閱了cctv.5這個頻道的全部客戶端都將收到這條消息。socket

圖片描述
除了訂閱頻道以外,客戶端還能夠經過執行PSUBSCRIBE命令訂閱一個或多個模式,從而成爲這些模式的訂閱者。每當有其餘客戶端向某個頻道發送消息時,消息不單單會發送給這個頻道的訂閱者,還會發送給與這個頻道相匹配的模式訂閱者。函數

圖片描述
上圖中,咱們給cctv.3這個頻道發送了一條消息"同一首歌",該消息首先會發送給它的訂閱者client A,而後根據模式匹配,cctv.3符合cctv.*這個模式。因此訂閱了cctv.*頻道的訂閱者也均可以收到消息呢。(●´∀`●)ノspa

頻道的訂閱命令行

當一個客戶端執行SUBSCRIBE命令訂閱某個或某些頻道的時候,這個客戶端與被訂閱頻道之間就創建了一種訂閱關係。code

Redis 將全部頻道的訂閱關係都保存在服務器狀態的pubsub_channels字典裏面,這個字典的鍵是某個被訂閱的頻道,值是一個鏈表,鏈表裏面記錄了全部訂閱這個頻道的客戶端。server

圖片描述

客戶端執行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)
相關文章
相關標籤/搜索