Redis source code analysis and comments (15) ---- notification function implementation and actual combat (notify)

Redis notification function implementation and actual combat

  1. Notification function introduction The
    client can receive events that change the Redis data set in some way through the subscription and publishing functions (pub/sub).

The current Redis subscription and publishing function adopts a fire and forget strategy. When a client subscribing to an event is disconnected, it will lose all events distributed to it during the disconnection.

  1. Types of notifications The types of
    notification functions are:

Key-space notification (key-space notification)
Key-event notification (key-event notification)

//notify.c
#define NOTIFY_KEYSPACE (1<<0)    /* K */   //键空间通知
#define NOTIFY_KEYEVENT (1<<1)    /* E */   //键事件通知

The format of these two notifications is as follows:

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

The source code for constructing these two notification formats is as follows:

// 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); //释放事件对象
}
  1. The configuration of the notification function The configuration of the
    notification function is turned off by default, because most users don't need this function, because there will be some additional overhead to enable this function. Two ways to turn on the notification function

Modify the notify-keyspace-events
CONFIG SET notify-keyspace-events in the configuration file redis.conf. The character
notify-keyspace-eventsparameter can be any combination of the following characters, which specifies what type of notification the server should send:

Insert picture description here
There must be at least one K or E in the input parameters, otherwise, no matter what the other parameters are, no notification will be distributed. For example, if KEA is set, it means sending all types of notifications.

The source code definitions of these characters:

// 键空间通知的类型,每个类型都关联着一个有目的的字符
#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 */   //所有键通知

These characters are usually saved by bitwise OR operation of multiple characters using a flags parameter of type int. So it involves the mutual conversion of flags and strings

String 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 string

// 根据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. Notification function actual combat
    Publish subscription function command key usage method

Client 1

127.0.0.1:6379> PSUBSCRIBE __key*           //执行接收所有类型的通知
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*"
3) (integer) 1
//此时客户端阻塞等待消息的发布

Client 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

Client 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"

Guess you like

Origin blog.csdn.net/qq_26249609/article/details/103986173