redisソースの概要-8-データベースの実装
12175 ワード
環境説明:redisソースバージョン5.0.3;ソースコードを読む過程でコメントをしましたgitアドレス:https://gitee.com/xiaoangg/redis_annotation参考書:『redisの設計と実現』
目次
サーバ内のデータベース
二切替データ
さんじげんライブラリキーくうかん
4キーの有効期限
5期限切れキー削除ポリシー
六削除ポリシーの実装
サーバ内のデータベース
redisサーバはすべてのデータベースをserverに保存します.h/redisServer構造のdb配列;配列の各項目はserverです.h/redisDb構造で、各構造はデータベースを表す.
struct redisServer {
/* General */
....
redisDb *db; //
....
int dbnum; /* Total number of configured DBs */
}
初期化データの数はdbnumによって決定される.デフォルトは16です.
二切替データ
各redisクライアントには独自のターゲットデータベース、クライアントステータスserverがある.h/client構造のdb属性記録は、クライアントの現在のターゲットデータベースに記録される.
/* With multiplexing we need to take per-client state.
* Clients are taken in a linked list. */
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
int fd; /* Client socket. */
redisDb *db; /* Pointer to currently SELECTed DB. */
.....
}
クライアントがselectコマンドを使用してターゲット・データベースを変更すると、クライアントのdbポインタは2番のデータベースを指定し、ターゲット・データベースを切り替える機能を実現します.
さんじげんライブラリキーくうかん
サーバ内の各データベースはserverである.h/redisDb構造は、dict辞書がデータベース内のすべてのキー値ペアに保存されることを表す.
/* 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. */
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; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
データベースのキー空間は辞書であるため、データベースに対するすべての操作は、辞書の操作に基づいて実現される.
1.新しいキーを追加する追加キーのエントリはdbにある.c/setKeyは、データベース内のすべてのkeyがこの方法で作成されます.
/*
key
*/
/* High level Set operation. This function can be used in order to set
* a key, whatever it was existing or not, to a new object.
*
* 1) The ref count of the value object is incremented.
* 2) clients WATCHing for the destination key notified.
* 3) The expire time of the key is reset (the key is made persistent).
*
* All the new keys in the database should be created via this interface. */
void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
} else {
dbOverwrite(db,key,val);
}
incrRefCount(val);// +1
removeExpire(db,key);
signalModifiedKey(db,key);
}
/*
key db ,
*/
/* Add the key to the DB. It's up to the caller to increment the reference
* counter of the value if needed.
*
* The program is aborted if the key already exists. */
void dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr);
int retval = dictAdd(db->dict, copy, val);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET)
signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key);
}
データベースにキーを追加し、最終的には辞書のdictAddメソッドを呼び出して実現されることがわかります.
2.削除キー
キータイムで呼び出されたディクショナリdict.c/dictGenericDeleteインタフェースを削除します.キー(次のコードのdictFreeKey(d,he)メソッドとキーに対応する値オブジェクト(次のコードのdictFreeValメソッド):
/* Search and remove an element. This is an helper function for
* dictDelete() and dictUnlink(), please check the top comment
* of those functions. */
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
uint64_t h, idx;
dictEntry *he, *prevHe;
int table;
//hash
if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;
if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key);
for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx];
prevHe = NULL;
while(he) {
if (key==he->key || dictCompareKeys(d, key, he->key)) {
/* Unlink the element from the list */
if (prevHe)
prevHe->next = he->next;
else
d->ht[table].table[idx] = he->next;
if (!nofree) {
dictFreeKey(d, he);
dictFreeVal(d, he);
zfree(he);
}
d->ht[table].used--;
return he;
}
prevHe = he;
he = he->next;
}
if (!dictIsRehashing(d)) break;
}
return NULL; /* not found */
}
3更新と検索操作
更新と検索操作は、値オブジェクトのタイプに応じて、対応する操作を行います.getコマンドを例に挙げます.
/*
*/
int getGenericCommand(client *c) {
robj *o;
//db.c/lookupKeyReadOrReply key
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
return C_OK;
// string
if (o->type != OBJ_STRING) {
addReply(c,shared.wrongtypeerr);
return C_ERR;
} else {
addReplyBulk(c,o);
return C_OK;
}
}
void getCommand(client *c) {
getGenericCommand(c);
}
4その他の操作
追加削除の変更に加えて、fulshdbはキー空間のすべてのキー値ペアを削除することです.randdomkeyはキー空間でランダムにキーを返す.dbsize、exist、rename、keysなどはキー空間を操作することによって実現される.
4キーの有効期限
EXPIREまたはPEXPIREコマンドにより、あるkeyの生存時間を設定でき、指定した秒数またはミリ秒数を経過すると、サーバは生存時間が0のキーを削除します.
1.有効期限の設定
redisには4つのコマンドでキーの生存時間EXPIRE、PEXPIR、EXPIREAT、PEXPIREATを設定できます.以下に、この4つのコマンドはすべてexipreであることがわかります.c/expireGenericCommand関数によって実現される.
/* EXPIRE key seconds */
void expireCommand(client *c) {
expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
/* EXPIREAT key time */
void expireatCommand(client *c) {
expireGenericCommand(c,0,UNIT_SECONDS);
}
/* PEXPIRE key milliseconds */
void pexpireCommand(client *c) {
expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
}
/* PEXPIREAT key ms_time */
void pexpireatCommand(client *c) {
expireGenericCommand(c,0,UNIT_MILLISECONDS);
}
2.保存期限
redisDbのexpires辞書は、データベース内のすべてのキーの有効期限を保存します.
/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
* after which the key will no longer be considered valid. */
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de;
/* Reuse the sds from the main dict in the expire dict */
kde = dictFind(db->dict,key->ptr); // key
serverAssertWithInfo(NULL,key,kde != NULL);
// db expire key key( key )
de = dictAddOrFind(db->expires,dictGetKey(kde));
dictSetSignedIntegerVal(de,when); //
int writable_slave = server.masterhost && server.repl_slave_ro == 0;
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
rememberSlaveKeyWithExpire(db,key);
}
3.有効期限の削除
期限切れ時間の削除は比較的簡単で、辞書インタフェースdictDeleteを呼び出してkeyを期限切れ辞書から削除するだけでよい.db.c/removeExpire
int removeExpire(redisDb *db, robj *key) {
/* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
return dictDelete(db->expires,key->ptr) == DICT_OK;
}
4.取得期限
期限切れの取得もよく理解でき、期限切れの辞書で対応するkeyを検索する必要があります.TTL、PTTLコマンドの実装ソースコードを直接貼り付ける.
//expire.c/ttlGenericCommand
/* Implements TTL and PTTL */
void ttlGenericCommand(client *c, int output_ms) {
long long expire, ttl = -1;
// key , -2
/* If the key does not exist at all, return -2 */
if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) {
addReplyLongLong(c,-2);
return;
}
//key , -1
/* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */
expire = getExpire(c->db,c->argv[1]);
if (expire != -1) {
ttl = expire-mstime();
if (ttl < 0) ttl = 0;
}
if (ttl == -1) {
addReplyLongLong(c,-1);
} else {
addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
}
}
//db.c/getExpire
/* Return the expire time of the specified key, or -1 if no expire
* is associated with this key (i.e. the key is non volatile) */
long long getExpire(redisDb *db, robj *key) {
dictEntry *de;
/* No expire? return ASAP */
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
/* The entry was found in the expire dict, this means it should also
* be present in the main dict (safety check). */
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
return dictGetSignedIntegerVal(de);
}
5期限切れキー削除ポリシー
keyの期限切れは期限切れの配列に保存されますが、keyはいつ削除されますか?
タイミング削除はメモリに友好的で、タイミング削除は期限切れのキーができるだけ早く削除され、キーが占有する空間を解放することを保証することができる.
ただし、cpuには友好的ではありません.期限切れキーが多い場合、期限切れキーを削除する行為はcpuの時間の相当部分を占め、サーバの時間とスループットに一定の影響を与えます.
2.不活性削除
不活性削除はcpu時間にとって最も友好的である:プログラムはキーを取る時に期限切れの検査を行うだけで、検査は現在の操作のキーに限られる.
欠点はメモリに友好的ではないことです.データベースの多くのキーが期限切れになっていて、これらのキーがアクセスされていない場合、これらのキーも削除されません.
3.定期的に削除
定期的な削除は、前の2つのポリシーの側面折衷と見なすことができます.
定期的に削除して一定時間ごとに削除操作を行い、実行時間と頻度を限定してcpu操作時間への影響を低減する.
六削除ポリシーの実装
1.不活性削除
不活性削除ポリシーの実装はdbにある.c/expireIfNeeded;
db.cのデータベースキー値検索関数(lookupKeyXXXX関数)はexpireIfNeededメソッドを呼び出し、keyが期限切れであるかどうかを確認します.
int expireIfNeeded(redisDb *db, robj *key) {
if (!keyIsExpired(db,key)) return 0;
/* If we are running in the context of a slave, instead of
* evicting the expired key from the database, we 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 1;
/* Delete the key */
server.stat_expiredkeys++;
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
2.定期的に削除
定期削除ポリシーの実装はdbにある.c/activeExpireCycle,redisのサーバがサーバを周期的に操作するたびに.c/databasesCron関数が実行されると、activeExpireCycleが実行されます.