9.8 数据库通知

数据库通知是Redis 2.8版本新增加的功能,这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。

举个例子,以下代码展示了客户端如何获取0号数据库中针对message键执行的所有命令:



127.0.0.1:6379> SUBSCRIBE _ _keyspace@0_ _:message
Reading messages... (press Ctrl-C to quit)
1) "subscribe"  // 
订阅信息
2) "__keyspace@0__:message"     
3) (integer) 1  
1) "message"    //
执行SET
命令
2) "_ _keyspace@0_ _:message"   
3) "set"        
1) "message"    //
执行EXPIRE
命令
2) "_ _keyspace@0_ _:message"   
3) "expire"     
1) "message"    //
执行DEL
命令
2) "_ _keyspace@0_ _:message"   
3) "del"        

根据发回的通知显示,先后共有SET、EXPIRE、DEL三个命令对键message进行了操作。

这一类关注“某个键执行了什么命令”的通知称为键空间通知(key-space notification),除此之外,还有另一类称为键事件通知(key-event notification)的通知,它们关注的是“某个命令被什么键执行了”。

以下是一个键事件通知的例子,代码展示了客户端如何获取0号数据库中所有执行DEL命令的键:



127.0.0.1:6379> SUBSCRIBE _ _keyevent@0_ _:del
Reading messages... (press Ctrl-C to quit)
1) "subscribe"  // 
订阅信息
2) "_ _keyevent@0_ _:del"
3) (integer) 1
1) "message"    //
键key
执行了DEL
命令
2) "_ _keyevent@0_ _:del"
3) "key"
1) "message"    //
键number
执行了DEL
命令
2) "_ _keyevent@0_ _:del"
3) "number"
1) "message"    //
键message
执行了DEL
命令
2) "_ _keyevent@0_ _:del"
3) "message"

根据发回的通知显示,key、number、message三个键先后执行了DEL命令。

服务器配置的notify-keyspace-events选项决定了服务器所发送通知的类型:

·想让服务器发送所有类型的键空间通知和键事件通知,可以将选项的值设置为AKE。

·想让服务器发送所有类型的键空间通知,可以将选项的值设置为AK。

·想让服务器发送所有类型的键事件通知,可以将选项的值设置为AE。

·想让服务器只发送和字符串键有关的键空间通知,可以将选项的值设置为K$。

·想让服务器只发送和列表键有关的键事件通知,可以将选项的值设置为El。

关于数据库通知功能的详细用法,以及notify-keyspace-events选项的更多设置,Redis的官方文档已经做了很详细的介绍,这里不再赘述。

在接下来的内容中,我们来看看数据库通知功能的实现原理。

9.8.1 发送通知

发送数据库通知的功能是由notify.c/notifyKeyspaceEvent函数实现的:



void notifyKeyspaceEvent(int type,char *event,robj *key,int dbid);

函数的type参数是当前想要发送的通知的类型,程序会根据这个值来判断通知是否就是服务器配置notify-keyspace-events选项所选定的通知类型,从而决定是否发送通知。

event、keys和dbid分别是事件的名称、产生事件的键,以及产生事件的数据库号码,函数会根据type参数以及这三个参数来构建事件通知的内容,以及接收通知的频道名。

每当一个Redis命令需要发送数据库通知的时候,该命令的实现函数就会调用notify-KeyspaceEvent函数,并向函数传递传递该命令所引发的事件的相关信息。

例如,以下是SADD命令的实现函数saddCommand的其中一部分代码:



void saddCommand(redisClient*c){
    // ...
    // 
如果至少有一个元素被成功添加,那么执行以下程序
    if (added) {
        // ...
        // 
发送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
    }
    // ...
}

当SADD命令至少成功地向集合添加了一个集合元素之后,命令就会发送通知,该通知的类型为REDIS_NOTIFY_SET(表示这是一个集合键通知),名称为sadd(表示这是执行SADD命令所产生的通知)。

以下是另一个例子,展示了DEL命令的实现函数delCommand的其中一部分代码:



voi delCommand(redisClient *c){
    int deleted=0,j;
    // 
遍历所有输入键
    for (j=1; j<c->argc; j++){
        // 
尝试删除键
        if (dbDelete(c->db,c->argv[j])){
            // ...
            // 
删除键成功,发送通知
            notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
                "del",c->argv[j],c->db->id);
            // ...
        }
    }
    // ...
}

在delCommand函数中,函数遍历所有输入键,并在删除键成功时,发送通知,通知的类型为REDIS_NOTIFY_GENERIC(表示这是一个通用类型的通知),名称为del(表示这是执行DEL命令所产生的通知)。

其他发送通知的函数调用notifyKeyspaceEvent函数的方式也和saddCommand、delCommand类似,只是给定的参数不同,接下来我们来看看notifyKeyspaceEvent函数的实现。

9.8.2 发送通知的实现

以下是notifyKeyspaceEvent函数的伪代码实现:



def notifyKeyspaceEvent(type, event, key, dbid):
    # 
如果给定的通知不是服务器允许发送的通知,那么直接返回
    if not(server.notify_keyspace_events & type):
        return
    # 
发送键空间通知
    if server.notify_keyspace_events & REDIS_NOTIFY_KEYSPACE:
        #
将通知发送给频道__keyspace@<dbid>__:<key>
        #
内容为键所发生的事件 <event>
        # 
构建频道名字
        chan = "__keyspace@{dbid}__:{key}".format(dbid=dbid, key=key)
        # 
发送通知
        pubsubPublishMessage(chan, event)
    # 
发送键事件通知
    if server.notify_keyspace_events & REDIS_NOTIFY_KEYEVENT:
        #
将通知发送给频道__keyevent@<dbid>__:<event>
        #
内容为发生事件的键 <key>
        # 
构建频道名字
        chan = "__keyevent@{dbid}__:{event}".format(dbid=dbid,event=event)
        # 
发送通知
        pubsubPublishMessage(chan, key)

notifyKeyspaceEvent函数执行以下操作:

1)server.notify_keyspace_events属性就是服务器配置notify-keyspace-events选项所设置的值,如果给定的通知类型type不是服务器允许发送的通知类型,那么函数会直接返回,不做任何动作。

2)如果给定的通知是服务器允许发送的通知,那么下一步函数会检测服务器是否允许发送键空间通知,如果允许的话,程序就会构建并发送事件通知。

3)最后,函数检测服务器是否允许发送键事件通知,如果允许的话,程序就会构建并发送事件通知。

另外,pubsubPublishMessage函数是PUBLISH命令的实现函数,执行这个函数等同于执行PUBLISH命令,订阅数据库通知的客户端收到的信息就是由这个函数发出的,pubsubPublishMessage函数具体的实现细节可以参考第18章。