Redis 頻道的訂閱與退訂

2018-08-02 14:56 更新

當(dāng)一個(gè)客戶端執(zhí)行 SUBSCRIBE 命令, 訂閱某個(gè)或某些頻道的時(shí)候, 這個(gè)客戶端與被訂閱頻道之間就建立起了一種訂閱關(guān)系。

Redis 將所有頻道的訂閱關(guān)系都保存在服務(wù)器狀態(tài)的 pubsub_channels 字典里面, 這個(gè)字典的鍵是某個(gè)被訂閱的頻道, 而鍵的值則是一個(gè)鏈表, 鏈表里面記錄了所有訂閱這個(gè)頻道的客戶端:

struct redisServer {

    // ...

    // 保存所有頻道的訂閱關(guān)系
    dict *pubsub_channels;

    // ...

};

比如說(shuō), 圖 IMAGE_PUBSUB_CHANNELS 就展示了一個(gè) pubsub_channels 字典示例, 這個(gè)字典記錄了以下信息:

  • client-1 、 client-2 、 client-3 三個(gè)客戶端正在訂閱 "news.it" 頻道。
  • 客戶端 client-4 正在訂閱 "news.sport" 頻道。
  • client-5 和 client-6 兩個(gè)客戶端正在訂閱 "news.business" 頻道。

訂閱頻道

每當(dāng)客戶端執(zhí)行 SUBSCRIBE 命令, 訂閱某個(gè)或某些頻道的時(shí)候, 服務(wù)器都會(huì)將客戶端與被訂閱的頻道在 pubsub_channels 字典中進(jìn)行關(guān)聯(lián)。

根據(jù)頻道是否已經(jīng)有其他訂閱者, 關(guān)聯(lián)操作分為兩種情況執(zhí)行:

  • 如果頻道已經(jīng)有其他訂閱者, 那么它在 pubsub_channels 字典中必然有相應(yīng)的訂閱者鏈表, 程序唯一要做的就是將客戶端添加到訂閱者鏈表的末尾。
  • 如果頻道還未有任何訂閱者, 那么它必然不存在于 pubsub_channels 字典, 程序首先要在 pubsub_channels 字典中為頻道創(chuàng)建一個(gè)鍵, 并將這個(gè)鍵的值設(shè)置為空鏈表, 然后再將客戶端添加到鏈表, 成為鏈表的第一個(gè)元素。

舉個(gè)例子, 假設(shè)服務(wù)器 pubsub_channels 字典的當(dāng)前狀態(tài)如圖 IMAGE_PUBSUB_CHANNELS 所示, 那么當(dāng)客戶端 client-10086 執(zhí)行命令:

SUBSCRIBE "news.sport" "news.movie"

之后, pubsub_channels 字典將更新至圖 IMAGE_AFTER_SUBSCRIBE 所示的狀態(tài), 其中用虛線包圍的是新添加的節(jié)點(diǎn):

  • 更新后的 pubsub_channels 字典新增了 "news.movie" 鍵, 該鍵對(duì)應(yīng)的鏈表值只包含一個(gè) client-10086 節(jié)點(diǎn), 表示目前只有 client-10086一個(gè)客戶端在訂閱 "news.movie" 頻道。
  • 至于原本就已經(jīng)有客戶端在訂閱的 "news.sport" 頻道, client-10086 的節(jié)點(diǎn)放在了頻道對(duì)應(yīng)鏈表的末尾, 排在 client-4 節(jié)點(diǎn)的后面。

SUBSCRIBE 命令的實(shí)現(xiàn)可以用以下偽代碼來(lái)描述:

def subscribe(*all_input_channels):

    # 遍歷輸入的所有頻道
    for channel in all_input_channels:

        # 如果 channel 不存在于 pubsub_channels 字典(沒(méi)有任何訂閱者)
        # 那么在字典中添加 channel 鍵,并設(shè)置它的值為空鏈表
        if channel not in server.pubsub_channels:
            server.pubsub_channels[channel] = []

        # 將訂閱者添加到頻道所對(duì)應(yīng)的鏈表的末尾
        server.pubsub_channels[channel].append(client)

退訂頻道

UNSUBSCRIBE 命令的行為和 SUBSCRIBE 命令的行為正好相反 —— 當(dāng)一個(gè)客戶端退訂某個(gè)或某些頻道的時(shí)候, 服務(wù)器將從 pubsub_channels 中解除客戶端與被退訂頻道之間的關(guān)聯(lián):

  • 程序會(huì)根據(jù)被退訂頻道的名字, 在 pubsub_channels 字典中找到頻道對(duì)應(yīng)的訂閱者鏈表, 然后從訂閱者鏈表中刪除退訂客戶端的信息。
  • 如果刪除退訂客戶端之后, 頻道的訂閱者鏈表變成了空鏈表, 那么說(shuō)明這個(gè)頻道已經(jīng)沒(méi)有任何訂閱者了, 程序?qū)?nbsp;pubsub_channels 字典中刪除頻道對(duì)應(yīng)的鍵。

舉個(gè)例子, 假設(shè) pubsub_channels 的當(dāng)前狀態(tài)如圖 IMAGE_BEFORE_UNSUBSCRIBE 所示, 那么當(dāng)客戶端 client-10086 執(zhí)行命令:

UNSUBSCRIBE "news.sport" "news.movie"

之后, 圖中用虛線包圍的兩個(gè)節(jié)點(diǎn)將被刪除, 如圖 IMAGE_AFTER_UNSUBSCRIBE 所示:

  • 在 pubsub_channels 字典更新之后, client-10086 的信息已經(jīng)從 "news.sport" 頻道和 "news.movie" 頻道的訂閱者鏈表中被刪除了。
  • 另外, 因?yàn)閯h除 client-10086 之后, 頻道 "news.movie" 已經(jīng)沒(méi)有任何訂閱者, 因此鍵 "news.movie" 也從字典中被刪除了。

UNSUBSCRIBE 命令的實(shí)現(xiàn)可以用以下偽代碼來(lái)描述:

def unsubscribe(*all_input_channels):

    # 遍歷要退訂的所有頻道
    for channel in all_input_channels:

        # 在訂閱者鏈表中刪除退訂的客戶端
        server.pubsub_channels[channel].remove(client)

        # 如果頻道已經(jīng)沒(méi)有任何訂閱者了(訂閱者鏈表為空)
        # 那么將頻道從字典中刪除
        if len(server.pubsub_channels[channel]) == 0:
            server.pubsub_channels.remove(channel)
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)