第二部分 單機數據庫的實現
數據庫
服務器中的數據庫
- Redis服務器的全部數據庫都保存在redisServer.db數組中,而數據庫的數量使用redisServer.dbnum屬性保存
切換數據庫
- 客戶端經過修改目標數據庫指針,讓它指向redisServer.db數組中的不一樣元素來切換不一樣的數據庫
數據庫鍵空間
- 數據庫主要由dict和expires兩個字典域構成,其中dict字典負責保存鍵值對,而expires字典則負責鍵的過時時間
- 由於數據庫由字典構成,所以對數據庫的操做都是創建在對字典操做之上
- 數據庫的鍵老是一個字符串對象,而值則能夠是任意一種Redis對象類型,包括字符串對象、哈希表對象、集合對象、列表對象、有序集合對象。

設置鍵的過時時間java
- expires字典的鍵指向數據庫中的某個鍵,而值則記錄了數據庫鍵的過時時間,過時時間以毫秒爲單位的UNIX時間戳
過時鍵刪除策略
-
三種不一樣的刪除策略redis
-
定時刪除算法
- 在設置一個鍵的同時,建立一個定時器,讓定時器在鍵過時時間來臨時,當即執行對鍵的刪除操做
- 優勢: 對內存友好,能儘快地將過時鍵佔用的內存釋放
- 缺點: 對時間不友好,若是過時鍵不少,那麼會佔用大量CPU時間,影響服務器響應時間和吞吐量
-
惰性刪除數據庫
- 聽任過時鍵無論,可是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過時,若是過時,就刪除該鍵;若是沒有過時,就返回該鍵
- 優勢:對時間友好,只有當取出過時鍵時,纔將該鍵刪除
- 缺點:對空間不友好,大量過時無用鍵佔用內存,由內存泄露的風險
-
按期刪除數組
- 每一個一段時間,程序就對數據庫進行一次檢查,刪除裏面的過時鍵,至於要刪除多少過時鍵,以及要檢查多少個數據庫,由算法決定
- 優勢: 對上面兩種策略的折衷。 對內存友好,對空間友好
- 關鍵是如何決定刪除操做執行的時常和頻率
-
Redis的過時鍵刪除策略緩存
- Redis使用的是
按期刪除
+ 惰性刪除
保證過時鍵必定能被刪除。併合理利用CPU時間和避免內存空間浪費
- 惰性刪除 : 在執行命令以前,對輸入的鍵進行過時檢查
- 按期刪除 : 在規定時間內,分屢次遍歷服務器中多個數據庫,從數據庫中的expires字典隨機檢查一部分鍵的過時時間,並刪除其中的過時鍵。
AOF、RDB和複製功能對過時鍵的處理
數據庫通知
RDB持久化
前置知識: 進程和子進程

能夠看出,子進程和父進程的代碼區是共享的,而數據區和PCB塊是父進程的副本。網絡
子PCB中的PID字段爲新分配子進程PID,數據集字段爲數據集地址。數據結構
父進程和子進程是能夠並行執行的。互不干擾。
RDB文件的建立與載入
- RDB持久化經過保存數據庫中的鍵值對來記錄數據庫的狀態 , 生成通過壓縮的二進制文件。
-
建立過程
- SAVE命令由服務器進程直接執行保存操做,所以該命令會阻塞服務器
- BGSAVE由子進程執行保存操做,因此該命令不會阻塞服務器
-
載入過程
- 若是服務器開啓了AOF持久化功能,那麼服務器會優先使用AOF文件還原數據庫狀態
- 若是AOF處於關閉狀態,服務器纔會使用RDB文件來還原數據庫狀態(前者丟失的數據更少)
自動間隔性保存
-
服務器狀態中會保存全部用save選項設置的保存條件,當任意一個保存條件被知足時,服務器會自動執行BGSAVE命令。
#redis.conf
格式: save 時間 修改次數
save 900 1 (900s內修改1次)
save 300 10
save 60 10000 (60s內修改10000次)
struct redisServer{
struct saveparam *saveparams; //記錄保存條件的數據
long long dirty; //修改計數器
time_t lastsave; //上一次執行保存的時間
}
RDB文件的結構
- 對於不一樣類型的鍵值對,RDB文件會使用不一樣的方式來保存他們

AOF持久化
AOF(Append Only File)持久化實現
- RDB持久化經過保存數據庫中的鍵值對來記錄數據庫狀態的不一樣
- AOF持久化是經過保存Redis服務器所執行的寫命令來記錄數據庫狀態的

AOF文件的載入與數據還原
- 服務器只要載入並從新執行保存在AOF文件中的命令(使用僞客戶端),就能夠還原數據庫原本的狀態了。
AOF重寫
- 爲了解決AOF體積膨脹的問題,提供了AOF重寫機制。AOF重寫能夠產生一個新的AOF文件,這個新的AOF文件和原有的AOF文件保存的數據庫狀態是同樣的,但體積更小
- AOF重寫是一個由歧義的名字,程序無需對現有AOF文件進行任何裝入、分析和寫入操做。它是經過讀取數據庫中的鍵值對來實現的。
- AOF重寫程序放在子進程中執行,此時服務器進程能夠繼續處理命令請求
- 子進程帶有服務器進程數據的副本(數據一致性問題),那麼若是在重寫過程當中有新的寫請求更改數據庫狀態,就會產生當前數據庫狀態與重寫後的AOF文件狀態不一致問題。
- 在執行BGREWRITEAOF命令時,Redis服務器會維護一個AOF重寫緩衝區,該緩衝區會在子進程建立新AOF文件期間,記錄服務器執行的全部寫命令。當子進程完成建立新AOF文件的工做後,服務器會將重寫緩衝區中的全部內容追加到新的AOF文件的末尾,使得新舊兩個AOF文件所保存的數據狀態一致。隨後,用新的AOF文件替換舊的AOF文件,以此來完成AOF文件重寫操做
-

-
在AOF重寫期間,服務器的執行工做
- 執行客戶端的命令
- 將執行後的寫命令追加到AOF緩衝區(保證舊的AOF文件完整)
- 將執行後的寫命令追加到AOF重寫緩衝區(用於解決數據不一致問題)
事件
Redis服務器是一個
事件驅動程序
,服務器處理的事件分爲文件事件和時間事件兩類
文件事件
- 文件事件處理器是基於
Reactor模式
實現的網絡通訊程序
- 文件事件處理器使用
IO多路複用
程序來同時監聽多個套接字。並根據套接字目前執行的任務來爲套接字關聯不一樣的事件處理器
- 當被監聽的套接字準備好執行鏈接應答(accept)、讀取(read)、寫入(write)、關閉(close)時,與操做對應的文件事件就會產生,這時文件事件處理器就會調用套接字以前關聯好的事件處理器來處理這些事件。
- 文件事件是對套接字操做的抽象,每次套接字變爲可應答(acceptable)、可寫(writeable)或者可讀(reable)時,相應的文件事件就會產生
- 文件事件分爲AE_READABLE事件(讀事件)和AE_WRITEABLE事件(寫事件)兩類

-
一次完整的客戶端與服務端鏈接事件示例
- Redis服務器運行時, 將鏈接應答處理器與 AE_READABLE事件關聯起來
- 當Redis客戶端發起鏈接時,那麼監聽套接字將產生AE_READABLE事件,觸發鏈接應答處理器執行。處理跟客戶端創建鏈接,並將客戶端套接字的AE_READABLE事件與命令請求處理器關聯起來
- 當客戶端向redis發起請求的時候,那麼客戶端套接字將產生AE_READABLE事件,而後由對應的命令請求處理器來處理。讀取客戶端的命令內容,並傳給相應程序執行。
- 那麼當redis準備好給客戶端響應數據以後,服務端會將AE_WRITEABLE事件跟命令回覆處理器關聯起來。當客戶端準備嘗試讀取響應數據時,客戶端套接字就會產生AE_WRITEABLE事件,觸發命令回覆處理器執行處理,將準備好的數據返回給客戶端。 當回覆寫完時,服務器就會解除客戶端套接字的AE_WRITABLE事件與命令回覆處理器之間的關聯。
時間事件
事件的調度與執行
客戶端
- 服務器狀態結構使用clients鏈表表示鏈接了多少個客戶端狀態,新添加的客戶端狀態會被放到鏈尾
- 客戶端狀態flags屬性使用不一樣標誌來表示客戶端的角色,以及客戶端當前所在狀態
- 輸入緩衝區記錄了客戶端發送的命令請求,這個緩衝區大小不超過1GB
- 客戶端使用argv , argc兩個屬性記錄命令的參數和個數 , 而cmd屬性記錄了客戶端要執行命令的實現函數

- 客戶端有固定大小緩衝區和可變大小緩衝區兩種, 其中固定大小緩衝區最大大小爲16KB , 而可變大小緩衝區(由多個緩衝區組成, 用鏈表連接)最大大小不能超過服務器設置的硬性限制值
-
輸出緩衝區限制值有兩種,若是輸出緩衝區的大小超過了服務器設置的硬性限制, 那麼客戶端會被當即關閉 ; 除此以外 ; 若是客戶端在必定時間內,一直超過服務器設置的軟性限制,那麼客戶端也會關閉.
#設置硬性 , 軟性連接
命令名 客戶端角色 硬性連接 軟性連接 軟性連接時長
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
- 客戶端關閉的緣由 : 網絡鏈接關閉 ; 發送了不合格時的命令請求 ; 成爲CLIENT KILL目標 ; 空轉時間超時 ; 輸出緩衝區的大小超出限制.
服務端