亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > 綜合 > 正文

Redis源碼閱讀筆記--數據庫redisDb

2024-07-21 02:52:18
字體:
來源:轉載
供稿:網友

一. 數據庫

Redis的數據庫使用字典作為底層實現,數據庫的增、刪、查、改都是構建在字典的操作之上的。 redis服務器將所有數據庫都保存在服務器狀態結構redisServer(redis.h/redisServer)的db數組(應該是一個鏈表)里:

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

在初始化服務器時,程序會根據服務器狀態的dbnum屬性來決定應該創建多少個數據庫:

struct redisServer { // .. //服務器中數據庫的數量 int dbnum; //..}

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

二、切換數據庫原理

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

127.0.0.1:6379> set msg 'Hello world'OK127.0.0.1:6379> get msg"Hello world"127.0.0.1:6379> select 2OK127.0.0.1:6379[2]> get msg(nil)127.0.0.1:6379[2]>

在服務器內部,客戶端狀態redisClient結構(redis.h/redisClient)的db屬性記錄了客戶端當前的目標數據庫,這個屬性是一個指向redisDb結構(redis.h/redisDb)的指針:

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

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

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

三、數據庫的鍵空間

1、數據庫的結構(我們只分析鍵空間和鍵過期時間)

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

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

2、鍵空間的初始化

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

//創建并初始化數據庫結構 for (j = 0; j < server.dbnum; j++) { // 創建每個數據庫的鍵空間 server.db[j].dict = dictCreate(&dbDictType,NULL); // ... // 設定當前數據庫的編號 server.db[j].id = j;}

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

3、鍵空間的操作

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

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

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

查找鍵值對函數–lookupKey

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

添加鍵值對–dbAdd

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

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

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

四、數據庫的過期鍵操作

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

// 創建并初始化數據庫結構 for (j = 0; j < server.dbnum; j++) { // 創建每個數據庫的過期時間字典 server.db[j].expires = dictCreate(&keyptrDictType,NULL); // 設定當前數據庫的編號 server.db[j].id = j; // .. }

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

1、過期鍵處理函數

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

/* * 將鍵 key 的過期時間設為 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)); // 設置鍵的過期時間 // 這里是直接使用整數值來保存過期時間,不是用 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、過期鍵刪除策略

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

惰性刪除函數–expireIfNeeded()

源碼如下:

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

定期刪除策略

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

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

刪除過期鍵對AOF、RDB和主從復制都有影響,等到了介紹相關功能時再討論。 今天就先到這里~


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩av手机在线观看| 国产精品久久久久久久久男| 大胆欧美人体视频| 青青久久av北条麻妃海外网| 91九色视频在线| 国产精品草莓在线免费观看| 欧美自拍大量在线观看| 国产精品亚洲一区二区三区| 色一情一乱一区二区| 亚洲电影av在线| 国产成一区二区| 亚洲综合日韩中文字幕v在线| 欧美裸身视频免费观看| 欧洲亚洲免费在线| 国产亚洲精品综合一区91| 日韩av手机在线看| 精品国产老师黑色丝袜高跟鞋| 成人国产精品一区| 成人免费看黄网站| 日韩中文字幕国产精品| 亚洲人成免费电影| 26uuu国产精品视频| 欧美激情久久久久| 91九色蝌蚪国产| 国产女同一区二区| 97人洗澡人人免费公开视频碰碰碰| 国产精品久久久久久久av电影| 亚洲电影中文字幕| 清纯唯美亚洲激情| 456国产精品| 97色在线观看| 在线精品91av| 欧美最猛性xxxxx亚洲精品| 国产成人精品一区二区在线| 日av在线播放中文不卡| 国产精品国内视频| 538国产精品视频一区二区| 欧美日韩aaaa| 久久久av网站| 亚洲一区二区三区在线免费观看| 日本成熟性欧美| 午夜精品福利视频| 国产精品久久久久久av福利软件| 久久久精品国产网站| 国内精品久久久久久久| 国产亚洲欧洲在线| 青青青国产精品一区二区| 亚洲国产精彩中文乱码av在线播放| 国产精品视频久久久久| 日韩av网址在线观看| 欧美午夜精品久久久久久人妖| 国产精品电影一区| 永久免费看mv网站入口亚洲| 亚洲精品久久久久国产| 社区色欧美激情 | 在线成人免费网站| 91精品国产高清久久久久久91| 超碰97人人做人人爱少妇| 91在线高清免费观看| 亚洲天堂一区二区三区| 91久久久久久国产精品| 一区二区成人精品| 中文字幕精品www乱入免费视频| 日韩电影在线观看免费| 爱福利视频一区| 亚洲伊人一本大道中文字幕| 免费91在线视频| 久久久久久国产精品三级玉女聊斋| 亚洲精品mp4| 91黑丝高跟在线| 国产免费一区视频观看免费| 国产主播喷水一区二区| 欧美有码在线视频| 日本一区二区在线免费播放| 国产精品日韩在线观看| 91精品国产色综合久久不卡98| 国产日韩欧美一二三区| 中文字幕久久久av一区| 7m精品福利视频导航| 亚洲一区制服诱惑| 这里只有精品丝袜| 亚洲国产精品va| 久久久久五月天| 亚洲人成伊人成综合网久久久| 日韩欧美在线播放| 色偷偷9999www| 国产精品亚洲综合天堂夜夜| 亚洲成在人线av| 日韩av网址在线观看| 日本一区二区不卡| 狠狠爱在线视频一区| 日韩中文字幕国产| 久精品免费视频| 精品自拍视频在线观看| 91精品在线观看视频| 欧美电影在线播放| 日韩精品视频三区| 久久久www成人免费精品| 亚洲国产一区二区三区在线观看| 欧美日韩亚洲精品一区二区三区| 亚洲一区二区在线播放| 亚洲永久在线观看| 亚洲精品视频免费| 美女精品视频一区| 亚洲人在线视频| 亚洲视频在线观看视频| 国产啪精品视频网站| 国产99久久精品一区二区| 情事1991在线| 中文字幕亚洲一区二区三区| 国产网站欧美日韩免费精品在线观看| 日韩激情av在线免费观看| 国产精品极品美女在线观看免费| 日韩欧美a级成人黄色| 亚洲精品视频免费在线观看| 欧美日韩国内自拍| 26uuu另类亚洲欧美日本一| 两个人的视频www国产精品| 精品国产乱码久久久久久婷婷| 欧美资源在线观看| 国产精品27p| 国产日韩在线免费| 高清欧美电影在线| 精品网站999www| 97精品一区二区视频在线观看| 欧美成人精品h版在线观看| 在线播放日韩专区| 久久久久久网址| 亚洲www在线观看| 久久精品欧美视频| 亚洲欧美另类人妖| 欧美成人午夜激情视频| 97超级碰在线看视频免费在线看| 国产成人高潮免费观看精品| 欧美国产日韩一区| 97超级碰在线看视频免费在线看| 性欧美长视频免费观看不卡| 欧美刺激性大交免费视频| 欧美电影在线免费观看网站| 精品福利在线视频| 国产精品pans私拍| 亚洲free嫩bbb| 黑人与娇小精品av专区| 国产亚洲精品高潮| 精品亚洲一区二区| 日韩久久免费视频| 中文字幕日韩免费视频| 性视频1819p久久| 精品一区精品二区| 亚洲第一页自拍| 亚洲女性裸体视频| 欧美在线免费看| 中文日韩在线视频| 日韩在线视频免费观看| 欧美性xxxx极品高清hd直播| 欧美激情第99页| 最新亚洲国产精品| 成人黄色影片在线| 欧美日韩一区二区在线| 欧美成人精品不卡视频在线观看| 91成人在线播放| 日日狠狠久久偷偷四色综合免费| 国产美女扒开尿口久久久| 97精品一区二区视频在线观看|