簡讀筆記-Redis設計與實現第二章

第二部分 單機數據庫的實現

數據庫


服務器中的數據庫

  • Redis服務器的全部數據庫都保存在redisServer.db數組中,而數據庫的數量使用redisServer.dbnum屬性保存

切換數據庫

  • 客戶端經過修改目標數據庫指針,讓它指向redisServer.db數組中的不一樣元素來切換不一樣的數據庫

數據庫鍵空間

  • 數據庫主要由dict和expires兩個字典域構成,其中dict字典負責保存鍵值對,而expires字典則負責鍵的過時時間
  • 由於數據庫由字典構成,所以對數據庫的操做都是創建在對字典操做之上
  • 數據庫的鍵老是一個字符串對象,而值則能夠是任意一種Redis對象類型,包括字符串對象、哈希表對象、集合對象、列表對象、有序集合對象。

設置鍵的過時時間java

  • expires字典的鍵指向數據庫中的某個鍵,而值則記錄了數據庫鍵的過時時間,過時時間以毫秒爲單位的UNIX時間戳

過時鍵刪除策略

  • 三種不一樣的刪除策略redis

    • 定時刪除算法

      • 在設置一個鍵的同時,建立一個定時器,讓定時器在鍵過時時間來臨時,當即執行對鍵的刪除操做
      • 優勢: 對內存友好,能儘快地將過時鍵佔用的內存釋放
      • 缺點: 對時間不友好,若是過時鍵不少,那麼會佔用大量CPU時間,影響服務器響應時間和吞吐量
    • 惰性刪除數據庫

      • 聽任過時鍵無論,可是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過時,若是過時,就刪除該鍵;若是沒有過時,就返回該鍵
      • 優勢:對時間友好,只有當取出過時鍵時,纔將該鍵刪除
      • 缺點:對空間不友好,大量過時無用鍵佔用內存,由內存泄露的風險
    • 按期刪除數組

      • 每一個一段時間,程序就對數據庫進行一次檢查,刪除裏面的過時鍵,至於要刪除多少過時鍵,以及要檢查多少個數據庫,由算法決定
      • 優勢: 對上面兩種策略的折衷。 對內存友好,對空間友好
      • 關鍵是如何決定刪除操做執行的時常和頻率
  • Redis的過時鍵刪除策略緩存

    • Redis使用的是 按期刪除 + 惰性刪除 保證過時鍵必定能被刪除。併合理利用CPU時間和避免內存空間浪費
    • 惰性刪除 : 在執行命令以前,對輸入的鍵進行過時檢查
    • 按期刪除 : 在規定時間內,分屢次遍歷服務器中多個數據庫,從數據庫中的expires字典隨機檢查一部分鍵的過時時間,並刪除其中的過時鍵。

AOF、RDB和複製功能對過時鍵的處理

  • 執行SAVE命令或者BGSAVE命令所產生的新RDB文件不會包含已過時的鍵
  • 執行BGREWRITEAOF命令所產生的重寫AOF文件不會包含已過時的鍵安全

    • 當一個過時鍵被刪除以後,服務器會追加一條DEL命令到現有的AOF文件末尾,顯示地刪除過時鍵
  • 當載入RDB or AOF文件時,會對文件保存的鍵進行檢查,過時的鍵會被忽略。
  • 從服務器即便發現過時鍵也不會主動刪除,而是等待主節點發來DEL命令,這種統1、中心化的過時鍵刪除策略能夠保證主從服務器的數據一致性。

數據庫通知

  • 當Redis命令對數據庫進行修改以後 , 服務器會根據配置向客戶端發出數據庫通知 (PUB/SUB)服務器

    • 鍵空間通知: 某個鍵執行了什麼命令(SET / EXPIRE / DEL)
  • 鍵事件通知: 某個命令被哪些鍵執行了 (KEY1 / KEY2 / KEY3)

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文件中全部命令都是以Redis命令請求協議的格式(文本協議)保存的
  • 命令請求會先保存到AOF緩衝區裏面,以後再按期寫入並同步到AOF文件中

    • 因爲內存和磁盤的輸入/輸出速度不匹配,所以會將數據先寫入緩衝區。系統提供了fsync, fdatasync兩個同步函數(系統調用),讓操做系統當即將緩衝區的數據寫入硬盤中,減小緩衝區因爲宕機而丟失數據的影響
  • appendfsync選項的不通值對AOF持久化功能的安全性和Redis服務器的性能有很大的影響

    • always : 每一個事件循環都將aof_buf緩衝區內容寫入同步到AOF文件
    • everysec(默認) : 每一個事件循環後,判斷上一次AOF是否間隔1S,若是是,則將aof_buf緩衝區內容寫入同步到AOF文件。 所以就算故障停機,緩存也只丟失1S的數據。
    • no : 何時將緩衝區內容同步到AOF文件中,由操做系統決定

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事件與命令回覆處理器之間的關聯。

時間事件

  • 時間事件分爲定時事件和週期性事件;定時事件只在指定時間到達一次,而週期性事件則每隔一段事件到達一次。
  • 服務器在通常狀況下只執行serverCorn函數一個時間事件,而且是週期性的(100ms一次)

    事件實現的三個屬性:
    id:時間事件全局ID    ,  when:事件到達時間    timeProc:事件處理函數
    與一個由事件節點構成的無序鏈表

事件的調度與執行

  • 文件事件和時間事件之間是合做關係,服務器會輪流處理這兩種事件,而且處理事件過程當中不會發生搶佔
  • 時間事件的實際處理事件一般會比設定的到達晚一些(由於沒法中斷文件事件)
  • ServerCron是Redis週期性事件的主要函數。 它的工做主要包括

    • 更新服務器的各種統計信息,如時間,內存佔用
    • 清理數據庫過時鍵值對
    • 嘗試進行AOF和RDB操做等等

客戶端

  • 服務器狀態結構使用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目標 ; 空轉時間超時 ; 輸出緩衝區的大小超出限制.

服務端

  • 一個命令請求從發送到完成要經歷的步驟:

    • 客戶端將命令請求發給服務器
    • 服務器讀取命令請求,並分析命令參數
    • 命令執行器根據參數查找命令的實現函數,而後執行實現函數並得出命令回覆

      • 執行預備操做: 如檢驗命令的格式 ; 內存是否足夠 ; 命令此時是否合法 ; 查看是否開啓事務
      • 調用命令實現函數
      • 執行後續操做: 更改統計信息,如耗費時長 ; 若是開啓了AOF還要往緩衝區寫數據 ; 若是它是master,那麼還要將數據同步到從服務器
    • 服務器將命令回覆返回給客戶端
  • ServerCron函數(每隔100ms執行一次,維護服務器相關資源,並作統計)

    • 更新服務器時間緩存
    • 更新LRU時鐘 (空轉時間 = LRU時鐘 - 某個鍵上次訪問時間 )
    • 更新服務器每秒執行的命令數 (統計吞吐量) ; 更新內存峯值
    • 處理SIGTERM信號(中斷信號)
    • 管理數據庫資源(檢查過時鍵)
    • 將AOF緩衝區內容寫入AOF (每次事件循環時都會作出檢查)
  • 服務器從啓動到可以處理客戶端請求通過的步驟

    • 初始化服務器狀態
    • 載入服務器配置
    • 初始化服務器數據結構
    • 還原數據庫狀態
    • 執行事件循環
相關文章
相關標籤/搜索