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 |
DEL 、 EXPIRE 、 RENAME 等類型無關的通用命令的通知 |
$ |
字符串命令的通知 |
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通知功能文檔中文版