Redis設計與實現——單機數據庫的實現


數據庫

服務器中的數據庫

redisClient切換數據庫

redis客戶端默認目標數據庫為0號數據庫,可以通過SELECT命令來切換目標數據庫。

客戶端狀態redisClient結構的db屬性記錄了客戶端當前的目標數據庫,這個屬性是指向redisdb結構的指針。

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

數據庫鍵空間

Redis是一個鍵值對數據庫服務器,服務器中的每個數據庫都由一個redis.h/redisDb結構表示,其中redisDB的dict字典保存了數據庫中的所有鍵值對,我們將這個字典稱為鍵空間。

typedef struct redisDb{
    // 數據庫鍵空間,保存着數據庫中的所有鍵值對
    dict *dict
} redisDb;

鍵空間和用戶所見的數據庫是直接對應的:

1)鍵空間的鍵也就是數據庫的鍵,每個鍵都是一個字符串對象。

2)鍵空間的值也就是數據庫的值,每個值可以是字符串對象,列表對象,哈希表對象,集合對象和有序集合對象中任意一種Redis對象。

e.g.

redis > SET message "hello world"
ok
redis > RPUSH alphabet "a" "b" "c"
3
redis > HSET book namr "Redis in Action"
1
redis > HSET book author "Josiah L. Carlson"
1
redis > HSET book publisher "Manning"
1

 

讀寫鍵空間時的維護操作

當使用Redis命令對數據庫進行讀寫時,服務器不僅對鍵空間執行指定的讀寫操作。還會執行一些額外的維護工作。

1)對一個鍵的讀取命中次數和未命中次數,在INFO stats命令的keyspace_hits屬性和keyspace_misses屬性中查看。

2)讀取一個鍵之后,服務器會更新鍵的LRU時間,這個值可以用於計算鍵的閑置時間。

3)讀取一個鍵發現鍵已經過期了,那么服務器會刪除這個過期鍵,然后才執行余下的其他操作。

4)如果有客戶端使用WATCH命令監視某個鍵,被修改之后會記為臟(dirty),讓事務程序注意到這修改。

5)每次修改一個鍵之后,都會對臟(dirty)鍵計數器的值增1,這個計數器會觸發服務器的持久化以及復制操作。

 6)鍵的修改觸發數據庫通知功能。

設置鍵的生存時間或過期時間

保存過期鍵:

typedef struct redisDb{
    //過期字典,保存着鍵的過期時間
    dict *expires;
} redisDb;

 

移除過期時間,計算並返回剩余生存時間,過期鍵的判定類似。

過期鍵刪除策略

 三種策略;定時刪除,惰性刪除,定期刪除。

redis的過期刪除策略

Redis服務器實際使用的是惰性刪除和定期刪除兩種策略:通過配合使用這兩種刪除策略,服務器可以很好地合理使用CPU時間和避免浪費內存空間之間取得平衡。

懶性刪除策略的實現:                                                                                                       定期刪除策略的實現:

             

AOF,RDB和復制功能對過期鍵的處理

 RDB持久化:

生成RDB文件將過濾過期鍵。

載入RDB文件,如果是主服務器模式運行,過濾過期鍵;如果是從服務器模式運行,則一並載入,主從數據同步會清空從節點數據,所以不會有影響。

 

AOF持久化:

同樣對寫AOF文件,會過濾過期鍵。

 

復制功能:

數據庫通知

總結

 

RDB持久化

RDB文件的創建和載入

 

保存RDB二進制文件,使用SAVE,BGSAVE,其中save命令會堵塞進程,而bgsave會啟動后台進程。如果啟動了AOF持久化,那優先載入AOF日志。

自動間隔性保存

#設置保存條件
save 900 1        服務器在900秒之內,對數據庫進行了至少1次修改
save 300 10      服務器在300秒之內,對數據庫進行了至少10次修改
save  60  10000  服務器在60秒之內,對數據庫進行了至少10000次修改

#實現
struct redisServer{
    //計入了保存條件的數組
    struct saveparam *saveparam
    //dirty修改計數器  表示服務器在上次保存后,對數據庫狀態共進行多少次修改
    long long dirty
    //上一次執行保存的時間
    time_t   lastsave       
}

struct saveparam {
    //秒數
    time_t  seconds;
    //修改數
    int changes
}

#檢查保存條件是否滿足,則每隔100毫秒周期性執行ServerCron函數,遍歷條件數組saveparam,對滿足條件的數據庫,計數器置為0,並更新上次保存時間。

 總結:

AOF持久化

與RDB持久化不同的是,aof持久化是通過寫命令來保存數據庫狀態,而RDB保存的是鍵值對。

 

AOF持久化實現

AOF持久化功能分為:命令追加,文件寫入,文件同步三個步驟。

命令追加:

struct redisServer{
    //AOF緩沖區     寫命令按照一定格式會追加到緩沖區
    sds aof_buf;
}

AOF文件的寫入與同步:

def eventLoop():
       while True :
            #處理文件事件,接受命令請求以及發送命令回復
             processFileEvents()
             #處理時間事件  類似於ServerCron定期執行函數
             processTimeEvents()
            #考慮是否將aof_buf中的內容寫入和保存到AOF文件里面,三個選項
             flushAppendOnlyFile()

AOF文件的載入與數據還原

AOF文件重寫

Redis服務器可以創建一個新的AOF文件來替代現有的AOF文件,新舊兩個AOF文件所保存的數據庫狀態相同。

但新的AOF文件不包含冗余命令,所以體積相對較小。

 

AOF后台重寫:

為了解決數據不一致問題,Redis服務器設置了一個AOF重寫緩沖區,這個緩沖區在服務器創建子進程之后開始使用

子進程完成AOF重寫工作之后,會向父進程發送一個信號,父進程會調用一個信號處理函數並執行以下操作:

1)將AOF重寫緩沖區中所有內容寫入新AOF文件中,這時新AOF保存的數據庫狀態==服務器當前數據庫狀態;

2)對新的AOF文件改名,原子替換舊的AOF文件;

注意:只有信號處理函數執行時會對服務器進程造成堵塞,對性能造成的影響降到最低

 

總結:

事件

文件事件:

I/O多路復用程序總是會將所有產生事件的套接字都放在一個隊列里面,並串行化地向文件事件分派器傳送套接字。

時間事件

服務器將所有時間事件都放在一個無序鏈表中,每當時間事件執行器運行時,它就會遍歷整個鏈表,查找所有已到達的時間事件,並調用相應的事件處理器。

Redis時間事件分為兩類:

定時事件:讓一段程序在指定的時間之后執行一次。

周期性事件:讓一段程序每隔指定時間就執行一次。

事件的調度與執行

 

總結:

客戶端

struct redisServer{
    //一個保存所有client的鏈表
    list *clients;
}

 

客戶端屬性

typedef  struct redisClient{
    //套接字描述符
    int fd;

    //名字
    robj *name;

    //標志,記錄客戶端角色,以及目前所處的狀態
    int flag;

   //輸入緩沖區 用於保存客戶端發出的命令請求
    sds querybuf;

//其他 如命令參數,參數個數,輸出緩沖區,身份認證,時間 }redisClient;

 客戶端的創建與關閉

當客戶端與服務器通過網絡建立連接時,服務器就會調用連接處理事件,為客戶端創建相應的客戶端狀態,並將新的客戶端狀態添加到服務器狀態結構clients鏈表的尾鏈。

偽客戶端:

Lua腳本的偽客戶端

AOF文件的偽客戶端

總結:

服務端

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM