天天看點

[redis設計與實作][8]資料庫Redis資料庫定義:

typedef struct redisdb {

dict *dict; /* the keyspace for this db */

dict *expires; /* timeout of keys with a timeout set */

dict *blocking_keys; /* keys with clients waiting for data (blpop) */

dict *ready_keys; /* blocked keys that received a push */

dict *watched_keys; /* watched keys for multi/exec cas */

int id;

long long avg_ttl; /* average ttl, just for stats */

} redisdb;

dict數組儲存所有的資料庫,redis初始化的時候,預設會建立16個資料庫

#define redis_default_dbnum 16

預設情況下,redis用戶端的目标資料庫是0 号資料庫,可以通過select指令切換。 注意,由于redis缺少擷取目前操作的資料庫指令,使用select切換需要特别注意

讀寫資料庫中的鍵值對的時候,redis除了對鍵空間執行指定操作外,還有一些額外的操作:

讀取鍵之後(讀和寫操作都會先讀取),記錄鍵空間命中或不命中次數

讀取鍵之後,更新鍵的lru

讀取時發現已經過期,會先删除過期鍵

如果有用戶端使用watch指令監視了key,會在修改後标記為dirty

修改之後,會對dirty鍵計數器加1,用于持久化和複制

如果開啟了資料庫通知,修改之後會發送相應通知

robj *lookupkeyreadorreply(redisclient *c, robj *key, robj *reply) {

robj *o = lookupkeyread(c->db, key);

if (!o) addreply(c,reply);

return o;

}

robj *lookupkeyread(redisdb *db, robj *key) {

robj *val;

//查詢是否已經過期

expireifneeded(db,key);

val = lookupkey(db,key);

if (val == null)

server.stat_keyspace_misses++;

else

server.stat_keyspace_hits++;

return val;

robj *lookupkey(redisdb *db, robj *key) {

dictentry *de = dictfind(db->dict,key->ptr);

if (de) {

robj *val = dictgetval(de);

/* update the access time for the ageing algorithm.

* don’t do it if we have a saving child, as this will trigger

* a copy on write madness. */

if (server.rdb_child_pid == –1 && server.aof_child_pid == –1)

//設定lru時間

val->lru = server.lruclock;

} else {

return null;

通過exprire或者pexpire指令,可以設定鍵的ttl,如果鍵的ttl為0,會被自動删除。

expires字典儲存了資料庫中所有鍵的過期時間。

過期字典的鍵是指向某個資料中的鍵對象

過期字段的值是long long類型的整數,儲存這個鍵的過期時間

void expirecommand(redisclient *c) {

expiregenericcommand(c,mstime(),unit_seconds);

void expiregenericcommand(redisclient *c, long long basetime, int unit) {

robj *key = c->argv[1], *param = c->argv[2];

long long when; /* unix time in milliseconds when the key will expire. */

if (getlonglongfromobjectorreply(c, param, &when, null) != redis_ok)

return;

if (unit == unit_seconds) when *= 1000;

when += basetime;

/* no key, return zero. */

if (lookupkeyread(c->db,key) == null) {

addreply(c,shared.czero);

/* expire with negative ttl, or expireat with a timestamp into the past

* should never be executed as a del when load the aof or in the context

* of a slave instance.

*

* instead we take the other branch of the if statement setting an expire

* (possibly in the past) and wait for an explicit del from the master. */

if (when <= mstime() && !server.loading && !server.masterhost) {

robj *aux;

redisassertwithinfo(c,key,dbdelete(c->db,key));

server.dirty++;

/* replicate/aof this as an explicit del. */

aux = createstringobject(“del“,3);

rewriteclientcommandvector(c,2,aux,key);

decrrefcount(aux);

signalmodifiedkey(c->db,key);

notifykeyspaceevent(redis_notify_generic,“del“,key,c->db->id);

addreply(c, shared.cone);

//放到expires字典中

setexpire(c->db,key,when);

addreply(c,shared.cone);

notifykeyspaceevent(redis_notify_generic,“expire“,key,c->db->id);

惰性删除:每次執行指令前,都會調用expireifneeded函數檢查是否過期,如果已經過期,改函數會删除過期鍵

定時删除:定時執行activeexpirecycletryexpire函數

int expireifneeded(redisdb *db, robj *key) {

mstime_t when = getexpire(db,key);

mstime_t now;

if (when < 0) return 0; /* no expire for this key */

/* don’t expire anything while loading. it will be done later. */

if (server.loading) return 0;

/* if we are in the context of a lua script, we claim that time is

* blocked to when the lua script started. this way a key can expire

* only the first time it is accessed and not in the middle of the

* script execution, making propagation to slaves / aof consistent.

* see issue #1525 on github for more information. */

now = server.lua_caller ? server.lua_time_start : mstime();

/* if we are running in the context of a slave, return asap:

* the slave key expiration is controlled by the master that will

* send us synthesized del operations for expired keys.

* still we try to return the right information to the caller,

* that is, 0 if we think the key should be still valid, 1 if

* we think the key is expired at this time. */

if (server.masterhost != null) return now > when;

/* return when this key has not expired */

if (now <= when) return 0;

/* delete the key */

server.stat_expiredkeys++;

propagateexpire(db,key);

notifykeyspaceevent(redis_notify_expired,

“expired“,key,db->id);

return dbdelete(db,key);

while (num–) {

dictentry *de;

long long ttl;

if ((de = dictgetrandomkey(db->expires)) == null) break;

ttl = dictgetsignedintegerval(de)-now;

if (activeexpirecycletryexpire(db,de,now)) expired++;

if (ttl < 0) ttl = 0;

ttl_sum += ttl;

ttl_samples++;

生成rdb檔案時,已過期的鍵不會被儲存到新的rdb檔案中

載入rdb檔案:

主伺服器載入時,會忽略過期鍵

從伺服器載入時,都會被載入(但是很快會因為同步被覆寫)

aof寫入,已過期未删除的鍵沒有影響,被删除後,會追加一條del指令

aof重寫,會對鍵進行檢查,過期鍵不會儲存到重寫後的aof檔案

複制:

主伺服器删除一個過期鍵後,會顯式向所有從伺服器發送del指令

從伺服器執行讀指令,及時過期也不會删除,隻有接受到主伺服器del指令才會删除

轉載自:https://coolex.info/blog/459.html