久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

數(shù)據(jù)庫redis Db與鍵過期刪除方法是什么

127次閱讀
沒有評論

共計 7315 個字符,預(yù)計需要花費 19 分鐘才能閱讀完成。

本篇內(nèi)容主要講解“數(shù)據(jù)庫 redis Db 與鍵過期刪除方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓丸趣 TV 小編來帶大家學(xué)習(xí)“數(shù)據(jù)庫 redis Db 與鍵過期刪除方法是什么”吧!

一. 數(shù)據(jù)庫

Redis 的數(shù)據(jù)庫使用字典作為底層實現(xiàn),數(shù)據(jù)庫的增、刪、查、改都是構(gòu)建在字典的操作之上的。
redis 服務(wù)器將所有數(shù)據(jù)庫都保存在服務(wù)器狀態(tài)結(jié)構(gòu) redisServer(redis.h/redisServer)的 db 數(shù)組(應(yīng)該是一個鏈表)里:

struct redisServer {
 //..
 //  數(shù)據(jù)庫數(shù)組,保存著服務(wù)器中所有的數(shù)據(jù)庫
 redisDb *db;
 //..
}

在初始化服務(wù)器時,程序會根據(jù)服務(wù)器狀態(tài)的 dbnum 屬性來決定應(yīng)該創(chuàng)建多少個數(shù)據(jù)庫:

struct redisServer {
 // ..
 // 服務(wù)器中數(shù)據(jù)庫的數(shù)量
 int dbnum;
 //..
}

dbnum 屬性的值是由服務(wù)器配置的 database 選項決定的,默認值為 16;

二、切換數(shù)據(jù)庫原理

每個 Redis 客戶端都有自己的目標數(shù)據(jù)庫,每當客戶端執(zhí)行數(shù)據(jù)庫的讀寫命令時,目標數(shù)據(jù)庫就會成為這些命令的操作對象。

127.0.0.1:6379  set msg  Hello world 
127.0.0.1:6379  get msg
 Hello world 
127.0.0.1:6379  select 2
127.0.0.1:6379[2]  get msg
(nil)
127.0.0.1:6379[2]

在服務(wù)器內(nèi)部,客戶端狀態(tài) redisClient 結(jié)構(gòu) (redis.h/redisClient) 的 db 屬性記錄了客戶端當前的目標數(shù)據(jù)庫,這個屬性是一個指向 redisDb 結(jié)構(gòu) (redis.h/redisDb) 的指針:

typedef struct redisClient {
 //..
 //  客戶端當前正在使用的數(shù)據(jù)庫
 redisDb *db;
 //..
} redisClient;

redisClient.db 指針指向 redisServer.db 數(shù)組中的一個元素,而被指向的元素就是當前客戶端的目標數(shù)據(jù)庫。
我們就可以通過修改 redisClient 指針,讓他指向服務(wù)器中的不同數(shù)據(jù)庫,從而實現(xiàn)切換數(shù)據(jù)庫的功能–這就是 select 命令的實現(xiàn)原理。
實現(xiàn)代碼:

int selectDb(redisClient *c, int id) {
 //  確保  id  在正確范圍內(nèi)
 if (id   0 || id  = server.dbnum)
 return REDIS_ERR;
 //  切換數(shù)據(jù)庫(更新指針) c- db =  server.db[id];
 return REDIS_OK;
}

三、數(shù)據(jù)庫的鍵空間 1、數(shù)據(jù)庫的結(jié)構(gòu)(我們只分析鍵空間和鍵過期時間)

typedef struct redisDb {
 //  數(shù)據(jù)庫鍵空間,保存著數(shù)據(jù)庫中的所有鍵值對
 dict *dict; /* The keyspace for this DB */
 //  鍵的過期時間,字典的鍵為鍵,字典的值為過期事件  UNIX  時間戳
 dict *expires; /* Timeout of keys with a timeout set */
 //  數(shù)據(jù)庫號碼
 int id; /* Database ID */
 //  數(shù)據(jù)庫的鍵的平均  TTL ,統(tǒng)計信息
 long long avg_ttl; /* Average TTL, just for stats */
 //..
} redisDb

上圖是一個 RedisDb 的示例,該數(shù)據(jù)庫存放有五個鍵值對,分別是 sRedis,INums,hBooks,SortNum 和 sNums,它們各自都有自己的值對象,另外,其中有三個鍵設(shè)置了過期時間,當前數(shù)據(jù)庫是服務(wù)器的第 0 號數(shù)據(jù)庫。現(xiàn)在,我們就從源碼角度分析這個數(shù)據(jù)庫結(jié)構(gòu):
我們知道,Redis 是一個鍵值對數(shù)據(jù)庫服務(wù)器,服務(wù)器中的每一個數(shù)據(jù)庫都是一個 redis.h/redisDb 結(jié)構(gòu),其中,結(jié)構(gòu)中的 dict 字典保存了數(shù)據(jù)庫中所有的鍵值對,我們就將這個字典成為鍵空間。
Redis 數(shù)據(jù)庫的數(shù)據(jù)都是以鍵值對的形式存在,其充分利用了字典高效索引的特點。
a、鍵空間的鍵就是數(shù)據(jù)庫中的鍵,一般都是字符串對象;
b、鍵空間的值就是數(shù)據(jù)庫中的值,可以是 5 種類型對象(字符串、列表、哈希、集合和有序集合)之一。
數(shù)據(jù)庫的鍵空間結(jié)構(gòu)分析完了,我們先看看數(shù)據(jù)庫的初始化。

2、鍵空間的初始化

在 redis.c 中,我們可以找到鍵空間的初始化操作:

// 創(chuàng)建并初始化數(shù)據(jù)庫結(jié)構(gòu)
 for (j = 0; j   server.dbnum; j++) {
 //  創(chuàng)建每個數(shù)據(jù)庫的鍵空間
 server.db[j].dict = dictCreate(dbDictType,NULL);
 // ...
 //  設(shè)定當前數(shù)據(jù)庫的編號
 server.db[j].id = j;
}

初始化之后就是對鍵空間的操作了。

3、鍵空間的操作

我先把一些常見的鍵空間操作函數(shù)列出來:

//  從數(shù)據(jù)庫中取出鍵 key 的值對象,若不存在就返回 NULL
robj *lookupKey(redisDb *db, robj *key);
/*  先刪除過期鍵,以讀操作的方式從數(shù)據(jù)庫中取出指定鍵對應(yīng)的值對象
 *  并根據(jù)是否成功找到值,更新服務(wù)器的命中或不命中信息,
 *  如不存在則返回 NULL,底層調(diào)用 lookupKey 函數(shù)  */
robj *lookupKeyRead(redisDb *db, robj *key);
/*  先刪除過期鍵,以寫操作的方式從數(shù)據(jù)庫中取出指定鍵對應(yīng)的值對象
 *  如不存在則返回 NULL,底層調(diào)用 lookupKey 函數(shù), *  不會更新服務(wù)器的命中或不命中信息
 */
robj *lookupKeyWrite(redisDb *db, robj *key);
/*  先刪除過期鍵,以讀操作的方式從數(shù)據(jù)庫中取出指定鍵對應(yīng)的值對象
 *  如不存在則返回 NULL,底層調(diào)用 lookupKeyRead 函數(shù)
 *  此操作需要向客戶端回復(fù)
 */
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply);
/*  先刪除過期鍵,以寫操作的方式從數(shù)據(jù)庫中取出指定鍵對應(yīng)的值對象
 *  如不存在則返回 NULL,底層調(diào)用 lookupKeyWrite 函數(shù)
 *  此操作需要向客戶端回復(fù)
 */
robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply);
/*  添加元素到指定數(shù)據(jù)庫  */
void dbAdd(redisDb *db, robj *key, robj *val);
/*  重寫指定鍵的值  */
void dbOverwrite(redisDb *db, robj *key, robj *val);
/*  設(shè)定指定鍵的值  */
void setKey(redisDb *db, robj *key, robj *val);
/*  判斷指定鍵是否存在  */
int dbExists(redisDb *db, robj *key);
/*  隨機返回數(shù)據(jù)庫中的鍵  */
robj *dbRandomKey(redisDb *db);
/*  刪除指定鍵  */
int dbDelete(redisDb *db, robj *key);
/*  清空所有數(shù)據(jù)庫,返回鍵值對的個數(shù)  */
long long emptyDb(void(callback)(void*));

下面我選取幾個比較典型的操作函數(shù)分析一下:

查找鍵值對函數(shù)–lookupKey

robj *lookupKey(redisDb *db, robj *key) {
 //  查找鍵空間
 dictEntry *de = dictFind(db- dict,key- ptr);
 //  節(jié)點存在
 if (de) {
 //  取出該鍵對應(yīng)的值
 robj *val = dictGetVal(de);
 //  更新時間信息
 if (server.rdb_child_pid == -1   server.aof_child_pid == -1)
 val- lru = LRU_CLOCK();
 //  返回值
 return val;
 } else {
 //  節(jié)點不存在
 return NULL;
 }
}

添加鍵值對–dbAdd
添加鍵值對使我們經(jīng)常使用到的函數(shù),底層由 dbAdd()函數(shù)實現(xiàn),傳入的參數(shù)是待添加的數(shù)據(jù)庫,鍵對象和值對象,源碼如下:

void dbAdd(redisDb *db, robj *key, robj *val) {
 //  復(fù)制鍵名
 sds copy = sdsdup(key- ptr);
 //  嘗試添加鍵值對
 int retval = dictAdd(db- dict, copy, val);
 //  如果鍵已經(jīng)存在,那么停止
 redisAssertWithInfo(NULL,key,retval == REDIS_OK);
 //  如果開啟了集群模式,那么將鍵保存到槽里面
 if (server.cluster_enabled) slotToKeyAdd(key);
 }

好了,關(guān)于鍵空間操作函數(shù)就分析到這,其他函數(shù) (在文件 db.c 中) 大家可以自己去分析,有問題的話可以回帖,我們可以一起討論!

四、數(shù)據(jù)庫的過期鍵操作

在前面我們說到,redisDb 結(jié)構(gòu)中有一個 expires 指針(概況圖可以看上圖),該指針指向一個字典結(jié)構(gòu),字典中保存了所有鍵的過期時間,該字典稱為過期字典。
過期字典的初始化:

// 創(chuàng)建并初始化數(shù)據(jù)庫結(jié)構(gòu)

 for (j = 0; j   server.dbnum; j++) {
 //  創(chuàng)建每個數(shù)據(jù)庫的過期時間字典
 server.db[j].expires = dictCreate(keyptrDictType,NULL);
 //  設(shè)定當前數(shù)據(jù)庫的編號
 server.db[j].id = j;
 // ..
 }

a、過期字典的鍵是一個指針,指向鍵空間中的某一個鍵對象(就是某一個數(shù)據(jù)庫鍵);
b、過期字典的值是一個 long long 類型的整數(shù),這個整數(shù)保存了鍵所指向的數(shù)據(jù)庫鍵的時間戳–一個毫秒精度的 unix 時間戳。
下面我們就來分析過期鍵的處理函數(shù):

1、過期鍵處理函數(shù)

設(shè)置鍵的過期時間–setExpire()

/*
 *  將鍵  key  的過期時間設(shè)為  when
 */
void setExpire(redisDb *db, robj *key, long long when) {
 dictEntry *kde, *de;
 //  從鍵空間中取出鍵 key
 kde = dictFind(db- dict,key- ptr);
 //  如果鍵空間找不到該鍵,報錯
 redisAssertWithInfo(NULL,key,kde != NULL);
 //  向過期字典中添加該鍵
 de = dictReplaceRaw(db- expires,dictGetKey(kde));
 //  設(shè)置鍵的過期時間
 //  這里是直接使用整數(shù)值來保存過期時間,不是用  INT  編碼的  String  對象
 dictSetSignedIntegerVal(de,when);
獲取鍵的過期時間–getExpire()
long long getExpire(redisDb *db, robj *key) {
 dictEntry *de;
 //  如果過期鍵不存在,那么直接返回
 if (dictSize(db- expires) == 0 ||
 (de = dictFind(db- expires,key- ptr)) == NULL) return -1;
 redisAssertWithInfo(NULL,key,dictFind(db- dict,key- ptr) != NULL);
 //  返回過期時間
 return dictGetSignedIntegerVal(de);
刪除鍵的過期時間–removeExpire()
//  移除鍵  key  的過期時間
int removeExpire(redisDb *db, robj *key) {
 //  確保鍵帶有過期時間
 redisAssertWithInfo(NULL,key,dictFind(db- dict,key- ptr) != NULL);
 //  刪除過期時間
 return dictDelete(db- expires,key- ptr) == DICT_OK;
}

2、過期鍵刪除策略

通過前面的介紹,大家應(yīng)該都知道數(shù)據(jù)庫鍵的過期時間都保存在過期字典里,那假如一個鍵過期了,那么這個過期鍵是什么時候被刪除的呢?現(xiàn)在來看看 redis 的過期鍵的刪除策略:
a、定時刪除:在設(shè)置鍵的過期時間的同時,創(chuàng)建一個定時器,在定時結(jié)束的時候,將該鍵刪除;
b、惰性刪除:放任鍵過期不管,在訪問該鍵的時候,判斷該鍵的過期時間是否已經(jīng)到了,如果過期時間已經(jīng)到了,就執(zhí)行刪除操作;
c、定期刪除:每隔一段時間,對數(shù)據(jù)庫中的鍵進行一次遍歷,刪除過期的鍵。
其中定時刪除可以及時刪除數(shù)據(jù)庫中的過期鍵,并釋放過期鍵所占用的內(nèi)存,但是它為每一個設(shè)置了過期時間的鍵都開了一個定時器,使的 cpu 的負載變高,會對服務(wù)器的響應(yīng)時間和吞吐量造成影響。
惰性刪除有效的克服了定時刪除對 CPU 的影響,但是,如果一個過期鍵很長時間沒有被訪問到,且若存在大量這種過期鍵時,勢必會占用很大的內(nèi)存空間,導(dǎo)致內(nèi)存消耗過大。
定時刪除可以算是上述兩種策略的折中。設(shè)定一個定時器,每隔一段時間遍歷數(shù)據(jù)庫,刪除其中的過期鍵,有效的緩解了定時刪除對 CPU 的占用以及惰性刪除對內(nèi)存的占用。
在實際應(yīng)用中,Redis 采用了惰性刪除和定時刪除兩種策略來對過期鍵進行處理,上面提到的 lookupKeyWrite 等函數(shù)中就利用到了惰性刪除策略,定時刪除策略則是在根據(jù)服務(wù)器的例行處理程序 serverCron 來執(zhí)行刪除操作,該程序每 100ms 調(diào)用一次。

惰性刪除函數(shù)–expireIfNeeded()
源碼如下:

/*  檢查 key 是否已經(jīng)過期,如果是的話,將它從數(shù)據(jù)庫中刪除  
 *  并將刪除命令寫入 AOF 文件以及附屬節(jié)點(主從復(fù)制和 AOF 持久化相關(guān))
 *  返回 0 代表該鍵還沒有過期,或者沒有設(shè)置過期時間
 *  返回 1 代表該鍵因為過期而被刪除
 */
int expireIfNeeded(redisDb *db, robj *key) {
 //  獲取該鍵的過期時間
 mstime_t when = getExpire(db,key);
 mstime_t now;
 //  該鍵沒有設(shè)定過期時間
 if (when   0) return 0;
 //  服務(wù)器正在加載數(shù)據(jù)的時候,不要處理
 if (server.loading) return 0;
 // lua 腳本相關(guān)
 now = server.lua_caller ? server.lua_time_start : mstime();
 //  主從復(fù)制相關(guān),附屬節(jié)點不主動刪除 key
 if (server.masterhost != NULL) return now   when;
 //  該鍵還沒有過期
 if (now  = when) return 0;
 //  刪除過期鍵
 server.stat_expiredkeys++;
 //  將刪除命令傳播到 AOF 文件和附屬節(jié)點
 propagateExpire(db,key);
 //  發(fā)送鍵空間操作時間通知
 notifyKeyspaceEvent(NOTIFY_EXPIRED,
  expired ,key,db- 
 //  將該鍵從數(shù)據(jù)庫中刪除
 return dbDelete(db,key);
}

定期刪除策略
過期鍵的定期刪除策略由 redis.c/activeExpireCycle()函數(shù)實現(xiàn),服務(wù)器周期性地操作 redis.c/serverCron()(每隔 100ms 執(zhí)行一次)時,會調(diào)用 activeExpireCycle()函數(shù),分多次遍歷服務(wù)器中的各個數(shù)據(jù)庫,從數(shù)據(jù)庫中的 expires 字典中隨機檢查一部分鍵的過期時間,并刪除其中的過期鍵。
刪除過期鍵的操作由 activeExpireCycleTryExpire 函數(shù) (activeExpireCycle() 調(diào)用了該函數(shù))執(zhí)行,其源碼如下:

/*  檢查鍵的過期時間,如過期直接刪除 */
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
 //  獲取過期時間
 long long t = dictGetSignedIntegerVal(de);
 if (now   t) {
 //  執(zhí)行到此說明過期
 //  創(chuàng)建該鍵的副本
 sds key = dictGetKey(de);
 robj *keyobj = createStringObject(key,sdslen(key));
 //  將刪除命令傳播到 AOF 和附屬節(jié)點
 propagateExpire(db,keyobj);
 //  在數(shù)據(jù)庫中刪除該鍵
 dbDelete(db,keyobj);
 //  發(fā)送事件通知
 notifyKeyspaceEvent(NOTIFY_EXPIRED,
  expired ,keyobj,db- 
 //  臨時鍵對象的引用計數(shù)減 1
 decrRefCount(keyobj);
 //  服務(wù)器的過期鍵計數(shù)加 1
 //  該參數(shù)影響每次處理的數(shù)據(jù)庫個數(shù)
 server.stat_expiredkeys++;
 return 1;
 } else {
 return 0;
 }
}

到此,相信大家對“數(shù)據(jù)庫 redis Db 與鍵過期刪除方法是什么”有了更深的了解,不妨來實際操作一番吧!這里是丸趣 TV 網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-07-26發(fā)表,共計7315字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 资源县| 望谟县| 临西县| 西乡县| 南乐县| 城固县| 兴隆县| 宽城| 靖安县| 高安市| 大化| 子洲县| 宜宾市| 岱山县| 丁青县| 临湘市| 图们市| 葵青区| 北海市| 古浪县| 石台县| 浑源县| 阳信县| 葵青区| 伊通| 香河县| 晋州市| 泰兴市| 甘谷县| 游戏| 巴彦淖尔市| 霍城县| 高邮市| 大石桥市| 来安县| 绍兴市| 东台市| 马边| 荆门市| 绍兴县| 晴隆县|