Redis源碼剖析和註釋(十五)---- 通知功能實現與實戰 (notify)

Redis 通知功能實現與實戰

1. 通知功能介紹

客戶端可以通過 訂閱與發佈功能(pub/sub)功能,來接收那些以某種方式改動了Redis數據集的事件。

目前Redis的訂閱與發佈功能採用的是發送即忘(fire and forget)的策略,當訂閱事件的客戶端斷線時,它會丟失所有在斷線期間分發給它的事件。

2. 通知的類型

通知功能的類型分別爲:

  • 鍵空間通知(key-space notification)
  • 鍵事件通知(key-event notification)
//notify.c
#define NOTIFY_KEYSPACE (1<<0)    /* K */   //鍵空間通知
#define NOTIFY_KEYEVENT (1<<1)    /* E */   //鍵事件通知

這兩種通知的格式如下:

__keyspace@<db>__:<key> <event> notifications.  //鍵空間通知格式
__keyevente@<db>__:<event> <key> notifications. //鍵事件通知格式

構建這兩種通知格式的源碼如下:

// event 是一個字符串類型的事件名
// key 是一個對象代表一個鍵名
// dbid 是數據庫id
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
    sds chan;
    robj *chanobj, *eventobj;
    int len = -1;
    char buf[24];

    /* If notifications for this class of events are off, return ASAP. */
    // 如果notify_keyspace_events中配置了不發送type類型的通知,則直接返回
    // notify_keyspace_events值爲 一個type的亦或值,type保存有不發送的通知
    if (!(server.notify_keyspace_events & type)) return;

    // 創建一個事件通知對象
    eventobj = createStringObject(event,strlen(event));

    /* __keyspace@<db>__:<key> <event> notifications. */
    // 發送 鍵空間 通知
    if (server.notify_keyspace_events & NOTIFY_KEYSPACE) {
        // 構建一個頻道對象,格式如上
        chan = sdsnewlen("__keyspace@",11);
        len = ll2string(buf,sizeof(buf),dbid);
        chan = sdscatlen(chan, buf, len);
        chan = sdscatlen(chan, "__:", 3);
        chan = sdscatsds(chan, key->ptr);
        chanobj = createObject(OBJ_STRING, chan);
        // 通過publish命令發送頻道對象chanobj和事件對象eventobj通知
        pubsubPublishMessage(chanobj, eventobj);
        decrRefCount(chanobj);  //釋放對象
    }

    /* __keyevente@<db>__:<event> <key> notifications. */
    // 發送 鍵事件 通知
    if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {
        // 構建一個頻道對象,格式如上
        chan = sdsnewlen("__keyevent@",11);
        if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
        chan = sdscatlen(chan, buf, len);
        chan = sdscatlen(chan, "__:", 3);
        chan = sdscatsds(chan, eventobj->ptr);
        chanobj = createObject(OBJ_STRING, chan);
        // 通過publish命令發送頻道對象chanobj和鍵key通知
        pubsubPublishMessage(chanobj, key);
        decrRefCount(chanobj);  //釋放對象
    }
    decrRefCount(eventobj); //釋放事件對象
}

3. 通知功能的配置

通知功能的配置默認是關閉狀態,因爲大多數使用者不需要這個功能,因爲開啓該功能會有一些額外的開銷。開啓通知功能的兩種方式

  • 修改配置文件redis.conf中的notify-keyspace-events
  • CONFIG SET notify-keyspace-events 字符

notify-keyspace-events參數可以是一下字符的任意組合,它指定了服務器該發送哪種類型的通知:

字符 發送的通知
K 鍵空間通知,所有通知以 __keyspace@<db>__ 爲前綴
E 鍵事件通知,所有通知以 __keyevent@<db>__ 爲前綴
g DELEXPIRERENAME 等類型無關的通用命令的通知
$ 字符串命令的通知
l 列表命令的通知
s 集合命令的通知
h 哈希命令的通知
z 有序集合命令的通知
x 過期事件:每當有過期鍵被刪除時發送
e 驅逐(evict)事件:每當有鍵因爲 maxmemory 政策而被刪除時發送
A 參數 g$lshzxe 的別名,包含所有的字符

輸入的參數中至少要有一個 K 或者 E ,否則的話,不管其餘的參數是什麼,都不會有任何通知被分發。例如,如果設置KEA那麼表示發送所有類型的通知。

這些字符的源碼定義:

// 鍵空間通知的類型,每個類型都關聯着一個有目的的字符
#define NOTIFY_KEYSPACE (1<<0)    /* K */   //鍵空間
#define NOTIFY_KEYEVENT (1<<1)    /* E */   //鍵事件
#define NOTIFY_GENERIC (1<<2)     /* g */   //通用無類型通知
#define NOTIFY_STRING (1<<3)      /* $ */   //字符串類型鍵通知
#define NOTIFY_LIST (1<<4)        /* l */   //列表鍵通知
#define NOTIFY_SET (1<<5)         /* s */   //集合鍵通知
#define NOTIFY_HASH (1<<6)        /* h */   //哈希鍵通知
#define NOTIFY_ZSET (1<<7)        /* z */   //有序集合鍵通知
#define NOTIFY_EXPIRED (1<<8)     /* x */   //過期有關的鍵通知
#define NOTIFY_EVICTED (1<<9)     /* e */   //驅逐有關的鍵通知
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED)      /* A */   //所有鍵通知

這些字符通常使用一個int類型的flags參數通過多個字符按位或運算保存起來。因此就涉及到flags和字符串的相互轉換

  • 字符串 to flags
// 對傳入的字符串參數進行分析,返回一個flags,flags保存字符串每個字符所映射的鍵空間事件類型
int keyspaceEventsStringToFlags(char *classes) {
    char *p = classes;
    int c, flags = 0;

    while((c = *p++) != '\0') {
        switch(c) {
        case 'A': flags |= NOTIFY_ALL; break;
        case 'g': flags |= NOTIFY_GENERIC; break;
        case '$': flags |= NOTIFY_STRING; break;
        case 'l': flags |= NOTIFY_LIST; break;
        case 's': flags |= NOTIFY_SET; break;
        case 'h': flags |= NOTIFY_HASH; break;
        case 'z': flags |= NOTIFY_ZSET; break;
        case 'x': flags |= NOTIFY_EXPIRED; break;
        case 'e': flags |= NOTIFY_EVICTED; break;
        case 'K': flags |= NOTIFY_KEYSPACE; break;
        case 'E': flags |= NOTIFY_KEYEVENT; break;
        default: return -1;
        }
    }
    return flags;
}
  • flags to 字符串
// 根據flags返回一個字符串,字符串中的字符就是設置flags的字符
sds keyspaceEventsFlagsToString(int flags) {
    sds res;

    res = sdsempty();
    if ((flags & NOTIFY_ALL) == NOTIFY_ALL) {
        res = sdscatlen(res,"A",1);
    } else {
        if (flags & NOTIFY_GENERIC) res = sdscatlen(res,"g",1);
        if (flags & NOTIFY_STRING) res = sdscatlen(res,"$",1);
        if (flags & NOTIFY_LIST) res = sdscatlen(res,"l",1);
        if (flags & NOTIFY_SET) res = sdscatlen(res,"s",1);
        if (flags & NOTIFY_HASH) res = sdscatlen(res,"h",1);
        if (flags & NOTIFY_ZSET) res = sdscatlen(res,"z",1);
        if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
        if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
    }
    if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
    if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
    return res;
}

4. 通知功能實戰

發佈訂閱功能的命令鍵使用方法

  • 客戶端1
127.0.0.1:6379> PSUBSCRIBE __key*           //執行接收所有類型的通知
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*"
3) (integer) 1
//此時客戶端阻塞等待消息的發佈
  • 客戶端2
127.0.0.1:6379> CONFIG SET notify-keyspace-events KEA   //設置接受所有通知,會改變server.notify_keyspace_events值
OK
127.0.0.1:6379> set number 888
OK
127.0.0.1:6379> DEL number 888
(integer) 1
127.0.0.1:6379> HSET hash field1 value1
(integer) 1
127.0.0.1:6379> EXPIRE hash 5           //設置生存時間爲5秒
(integer) 1
  • 客戶端1
127.0.0.1:6379> PSUBSCRIBE __key*           //執行接收所有類型的通知
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*"
3) (integer) 1
//此時客戶端阻塞等待消息的發佈,當客戶端2發佈消息時,會打印一下信息
1) "pmessage"                   //set number 888 命令所生成的通知
2) "__key*"
3) "__keyspace@0__:number"
4) "set" 
1) "pmessage"
2) "__key*"
3) "__keyevent@0__:set"
4) "number"

1) "pmessage"                   //DEL number 888 命令所生成的通知
2) "__key*"
3) "__keyspace@0__:number"
4) "del"  
1) "pmessage"
2) "__key*"
3) "__keyevent@0__:del"
4) "number"

1) "pmessage"                   //HSET hash field1 value1 命令所生成的通知
2) "__key*"
3) "__keyspace@0__:hash"
4) "hset"  
1) "pmessage"
2) "__key*"
3) "__keyevent@0__:hset"
4) "hash"

1) "pmessage"                   //EXPIRE hash 5 命令所生成的通知
2) "__key*"
3) "__keyspace@0__:hash"
4) "expire" 
1) "pmessage"
2) "__key*"
3) "__keyevent@0__:expire"
4) "hash"

1) "pmessage"                   //當hash鍵的5秒的生存時間到時後,自動發送 expired 通知
2) "__key*"
3) "__keyspace@0__:hash"
4) "expired"  
1) "pmessage"
2) "__key*"
3) "__keyevent@0__:expired"
4) "hash"

更詳細的內存可以參考:Redis通知功能文檔中文版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章