在之前的学习中我们了解了redis所有的数据结构和使用方法,现在我们开始来学习redis数据库相关的内容。
这一节先来学习服务器中的数据库,主要代码在server.h和Db.c中。
redis服务器的所有数据库都保存在服务器状态redisServer结构体的db数组中,db数据的每一个项都是redisDb结构,每个redisDb结构代表一个数据库,其中dict字典保存了数据库中的所有键值对,这个dict就是数据库的建空间;
键空间和用户所见的数据库是直接对应的,键空间的键也就是数据库的键,每个都是一个字符串对象,键空间的值也就是数据库的值,可以是任何一种redis对象;
redisDb结构体:
/* Redis database representation. There are multiple databases identified by integers from 0 (the default database) up to the max configured database. The database number is the 'id' field in the structure. 这个多数据库认证使用从0开始的整数指导最大值,这个数就是id*/
typedef struct redisDb {
dict *dict; /* The keyspace for this DB 数据库的键空间*/
dict *expires; /* Timeout of keys with a timeout set 过期key的字典*/
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) 客户端等带数据的key*/
dict *ready_keys; /* Blocked keys that received a PUSH 被阻止的key,收到推送*/
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS 事物watch的key*/
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys 去除池的key*/
int id; /* Database ID 数据库id*/
long long avg_ttl; /* Average TTL, just for stats 平均过期时间*/
} redisDb;
键空间的增删改查:
1、增:添加一个新键值对到数据库,实际上就是将一个新的键值对添加到键空间的字典中;
2、删:删除数据库中的一个键,就是在键空间删除键所对应的键值对对象;
3、改:就是对键空间中键所对应的值进行更新;
4、查:对一个数据库的键进行取值,就是在键空间中取出值对象,根据值类型的不同,方法也有所不同;
其他操作:
flushdb,randomkey,dbsize,exists,rename,keys等操作都是通过对键空间操作完成的。
读写键空间时的维护操作:
当redis对数据库进行读写时,服务器还会额外的进行一些维护操作:
1、读取一个键之后,服务器会根据键是否存在来更新键空间命中次数或者不命中次数;
2、在读取一个键之后,服务器会更新LRU时间;
3、在读取时发现键已过期,服务器会先删除这个键,然后执行其他操作;
4、如果客户端watch了某个键,那么服务器在对被监视的键进行修改后,会将这个键标记为dirty,从而让事务程序发现这个键已经被修改;
5、服务器每次修改一个键之后,都会对脏键计数器增1,会触发持久化及复制操作;
6、如果服务器开启了数据库通知功能,那么在对键进行修改后,会按配置发送相应的数据库通知。
redis键的过期时间:
过期键会保存在expire的字典中,这个字典的键是指针,指向键空间中的某个键对象,值是long long的指数,一个时间戳。
过期键的删除策略:
1、惰性删除:每次访问时判断该键是否应该删除;
2、定期删除:函数activeexpireCycle函数每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,删除过期键;
数据库通知:
发送数据库通知的功能是由notify.c/notifyKeyspaceEvent函数实现的;当一个redis命令需要发送通知的时候,就该调用这个函数,并向其传递事件的相关信息;
notify.c比较短,就在这里看吧:
#include "server.h"
/* Turn a string representing notification classes into an integer representing notification classes flags xored. The function returns -1 if the input contains characters not mapping to any class. 将表示通知的字符串转化为整数,如果不映射到任何字符,则返回-1*/
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;
}
/* This function does exactly the revese of the function above: it gets as input an integer with the xored flags and returns a string representing the selected classes. The string returned is an sds string that needs to be released with sdsfree(). 这个函数完成上面的函数:输入一个整数,返回一个表示所选类的字符串。这个返回的字符串需要使用sdsfree释放*/
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;
}
/* The API provided to the rest of the Redis core is a simple function: notifyKeyspaceEvent(char *event, robj *key, int dbid);'event' is a C string representing the event name. 'key' is a Redis object representing the key name. 'dbid' is the database ID where the key lives. event是字符串的时间名称,key是redis的最想指针,也就是产生时间的键,dbid是产生事件的数据库号码*/
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. 如果给定的通知不是服务器允许发送的通知,则返回*/
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);
pubsubPublishMessage(chanobj, eventobj);//发送通知
decrRefCount(chanobj);//对象引用计数+1
}
/* __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);
pubsubPublishMessage(chanobj, key);
decrRefCount(chanobj);//对象引用计数+1
}
decrRefCount(eventobj);
}