標籤:
概要redis的每個server執行個體都維護著一個儲存伺服器狀態的redisServer結構struct redisServer{ /* Pubsub */ // 字典,鍵為頻道,值為鏈表 // 鏈表中儲存了所有訂閱某個頻道的用戶端 // 新用戶端總是被添加到鏈表的表尾 dict *pubsub_channels; /* Map channels to list of subscribed clients */ // 這個鏈表記錄了客訂閱的所有模式的名字 list *pubsub_patterns; /* A list of pubsub_patterns */};pubsub_channels記錄了所有客訂閱的頻道的資訊。
redis的發布訂閱模式
訂閱者通過sub命令訂閱頻道,server使用pub命令把訊息推送到合格pubsub_channels中。
內部資料結構pubsub_channels是一個字典結構,字典內部使用hash表格儲存體和索引資料。
實現字典的可選資料結構
hash表:簡單但是不穩定的基於數組的衝突解決法;簡單且平均效率穩定的鏈式地址的衝突解決法;
字典樹:使用樹結構。redis採用hash的鏈式地址法作為實現,好處是直觀、簡單、可期待平均時間複雜度較小且平穩。typedef struct dict { // 類型特定函數 dictType *type; // 私人資料 void *privdata; // 雜湊表 dictht ht[2]; // rehash 索引 // 當 rehash 不在進行時,值為 -1 int rehashidx; /* rehashing not in progress if rehashidx == -1 */ // 目前正在啟動並執行安全迭代器的數量 int iterators; /* number of iterators currently running */} dict;dict使用兩個hash表進行索引,當進行rehash的時候使用ht[1]的hash表,其他時候都使用hd[0]的hash表。使用的hash表的定義如下typedef struct dictht { // 雜湊表數組 dictEntry **table; // 雜湊表大小 unsigned long size; // 雜湊表大小掩碼,用於計算索引值 // 總是等於 size - 1 unsigned long sizemask; // 該雜湊表已有節點的數量 unsigned long used;} dictht;table是一個儲存dictEntry*指標的數組,table中的每一項都是一個指向DictEntry結構的指標,同時每一項也都帶有一個指向下一項的指標。可以看出這是一個使用鏈地址法解決衝突的hash結構。dictEntry的定義,包含key、value、next。/* * 雜湊表節點 */typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 指向下個雜湊表節點,形成鏈表 struct dictEntry *next;} dictEntry;
客訂閱頻道就是字典中插入新元素的過程 // 關聯 // { // 頻道名 訂閱頻道的用戶端 // ‘channel-a‘ : [c1, c2, c3], // ‘channel-b‘ : [c5, c2, c1], // ‘channel-c‘ : [c10, c2, c1] // } /* Add the client to the channel -> list of clients hash table */ // 從 pubsub_channels 字典中取出儲存著所有訂閱了 channel 的用戶端的鏈表 // 如果 channel 不存在於字典,那麼添加進去 de = dictFind(server.pubsub_channels,channel); if (de == NULL) { clients = listCreate(); dictAdd(server.pubsub_channels,channel,clients); incrRefCount(channel); } else { clients = dictGetVal(de); } // before: // ‘channel‘ : [c1, c2] // after: // ‘channel‘ : [c1, c2, c3] // 將用戶端添加到鏈表的末尾 listAddNodeTail(clients,c);1.查詢server是否包含指定頻道2.如果頻道存在就擷取頻道指向的list地址;如果頻道不存在,就建立頻道和list3.使用步驟2的list,將用戶端添加到list的末尾。完成這三步以後,server維護的發布訂閱頻道就新增了一個頻道和關注的用戶端,server發布時檢測發布訂閱字典,擷取訂閱用戶端並依次發送。
server發布訊息到channel/* Send to clients listening for that channel */ // 取出包含所有訂閱頻道 channel 的用戶端的鏈表 // 並將訊息發送給它們 de = dictFind(server.pubsub_channels,channel); if (de) { list *list = dictGetVal(de); listNode *ln; listIter li; // 遍曆用戶端鏈表,將 message 發送給它們 listRewind(list,&li); while ((ln = listNext(&li)) != NULL) { redisClient *c = ln->value; // 回複用戶端。 // 樣本: // 1) "message" // 2) "xxx" // 3) "hello" addReply(c,shared.mbulkhdr[3]); // "message" 字串 addReply(c,shared.messagebulk); // 訊息的來源頻道 addReplyBulk(c,channel); // 訊息內容 addReplyBulk(c,message); // 接收用戶端計數 receivers++; } }結合訂閱者訂閱的過程,發布過程就是一個尋找訂閱者並輪詢發送訊息給訂閱者的過程。
redis的發布訂閱模式