Redis通知機能の実装と実際の戦闘
- 通知機能
の概要クライアントは、サブスクリプションおよびパブリッシュ機能(pub / sub)を介して、何らかの方法でRedisデータセットを変更するイベントを受信できます。
現在のRedisサブスクリプションおよび公開機能は、ファイアアンドフォーゲット戦略を採用しています。イベントをサブスクライブしているクライアントが切断されると、切断中に配信されたすべてのイベントが失われます。
- 通知の種類
通知機能の種類は次のとおりです。
キースペース通知(キースペース通知)
キーイベント通知(キーイベント通知)
//notify.c
#define NOTIFY_KEYSPACE (1<<0) /* K */ //键空间通知
#define NOTIFY_KEYEVENT (1<<1) /* E */ //键事件通知
これら2つの通知の形式は次のとおりです。
__keyspace@<db>__:<key> <event> notifications. //键空间通知格式
__keyevente@<db>__:<event> <key> notifications. //键事件通知格式
これら2つの通知形式を作成するためのソースコードは次のとおりです。
// 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); //释放事件对象
}
- 通知機能の構成通知機能の構成は
デフォルトでオフになっています。これは、この機能をオンにするために余分なオーバーヘッドが発生するため、ほとんどのユーザーがこの機能を必要としないためです。通知機能をオンにする2つの方法
構成ファイルredis.confのCONFIGSET notify-keyspace-events
notify- keyspace -eventsを変更します。character
notify-keyspace-events
パラメーターは、サーバーが送信する通知のタイプを指定する次の文字の任意の組み合わせにすることができます。
入力パラメーターには少なくとも1つの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パラメーターを使用した複数の文字のビットごとのOR演算によって保存されます。したがって、フラグと文字列の相互変換が含まれます
フラグへの文字列
// 对传入的字符串参数进行分析,返回一个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返回一个字符串,字符串中的字符就是设置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;
}
- 通知機能実際の戦闘
パブリッシュサブスクリプション機能コマンドキーの使用方法
クライアント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"