redis持久化

Redis持久化

redis是內存數據庫,所有的數據庫狀態、鍵值對都存儲在內存中,爲了避免數據丟失,可以將數據持久化到磁盤上。redis服務器啓動時可以根據持久化文件還原數據庫的狀態。redis的持久化有兩種方式:RDB持久化和AOF持久化。

先來簡單比較下這兩種持久化的不同,RDB持久化是將內存鍵值對的映射寫入到磁盤,AOF持久化是將對redis庫的寫命令寫入磁盤。redis服務器啓動的時候可以根據RDB或者AOF的持久化文件還原數據庫,如果是RDB文件,將RDB的鍵值對映射載入到內存中即可,如果是AOF文件,服務器會產生一個僞客戶端(無網絡連接的客戶端),這個僞客戶端執行從AOF文件讀取的一系列寫命令,最終還原數據庫的狀態。

如果同時存在RDB文件和AOF文件,服務器將會從AOF文件加載數據庫,否則有哪個文件就從哪個文件加載數據庫。

RDB持久化

RDB的持久化是將鍵值對寫入到磁盤的過程。通過執行SAVE或者BGSAVE命令,生成RDB文件。

SAVE命令

redis在處理客戶端請求時採用了單線程的方式,即所有的客戶端請求是通過單線程處理的。SAVE命令會阻塞服務器進程,在RDB文件持久化完成之前,redis服務器將無法處理客戶端請求。RDB文件持久化完成之後,服務器可以正常處理客戶端的請求。

BGSAVE命令

SAVE命令的缺點是很明顯的,它會阻塞服務器,所有客戶端請求都得不到處理,用戶體驗較差。BGSAVE命令通過fork一個子進程來處理RDB文件的持久化,服務器可以繼續處理客戶端的請求,子進程負責RDB文件的創建工作。

在BGSAVE命令執行期間,客戶端發送的SAVE命令和BGSAVE命令會被服務器拒絕。

服務器啓動時載入RDB文件期間,會處於阻塞狀態,客戶端請求將會被掛起。

redis提供了配製項,在條件滿足的情況下,自動執行BGSAVE命令。下面3行是redis的默認配製項:

save 900 1
save 300 10
save 60 10000

這3行配製的意思分別是:在900秒之內對數據庫進行了至少1次修改、在300秒之內對數據庫進行了至少10次修改、在60秒之內進行了至少10000次修改。滿足以上任一條件就會觸發BGSAVE命令。

配製文件中的配製項最終會被保存在redisServer結構體的saveparams屬性中:

//服務器狀態的結構體
struct redisServer {
  //……
  struct saveparam *saveparams;
};

//BGSAVE自動觸發條件的結構體
struct saveparam {
  //秒數
  time_t seconds;
  //修改次數
  int changes;
};

RDB文件結構

下圖是RDB的文件結構示意圖,RDB文件的開頭是5個字節,即”REDIS”。db_version是一個字符串表示的整數,這個值表示了RDB文件的版本號,這個字段長度爲4字節。databases包含着0個或者任意多個數據庫。EOF長度爲1個字節,表示了RDB文件的結束。check_sum是通過REDIS、db_version、databases、EOF計算出來的校驗和,長度爲8個字節。redis服務器載入RDB文件後會重新計算校驗和,與check_sum比較,若相同說明RDB文件沒有損壞,否則說明RDB文件已經損壞。

這裏寫圖片描述

圖1 RDB文件結構

這裏重點研究下databases部分,databases可以包括多個非空數據庫,例如包括0和1號兩個數據庫,數據庫的結構參考下圖:

這裏寫圖片描述

圖2 數據庫結構

其中SELECTED是長度爲1字節的常量,服務器讀到該值時,它將知道接下來讀到的是數據庫號碼db_number。key_value_paris真正保存了數據庫的鍵值對。key_value_paris分爲不帶過期時間的鍵值對和帶有過期時間的鍵值對。參考圖3和圖4

這裏寫圖片描述

圖3 不帶過期時間的鍵值對

這裏寫圖片描述

圖4 帶過期時間的鍵值對

其中TYPE表示了該鍵值對value的類型,它是一個常量,取值範圍爲:

  • REDIS_RDB_TYPE_STRING
  • REDIS_RDB_TYPE_LIST
  • REDIS_RDB_TYPE_SET
  • REDIS_RDB_TYPE_ZSET
  • REDIS_RDB_TYPE_HASH
  • REDIS_RDB_TYPE_LIST_ZIPLIST
  • REDIS_RDB_TYPE_SET_INTSET
  • REDIS_RDB_TYPE_ZSET_ZIPLIST
  • REDIS_RDB_TYPE_HASH_ZIPLIST

服務器讀到該值後,知道如何處理value部分。

帶過期時間的鍵值對結構,其中EXPIRETIME_MS是一個長度爲1字節的常量,服務器讀到該值後知道這個鍵值對是帶過期時間的,接下來讀到的8字節長的ms字段即是過期時間,單位爲毫秒。

AOF持久化

與RDB持久化不同的是,AOF是通過記錄服務器執行的寫命令來記錄服務器狀態的。AOF文件有自己的特定的文件格式來記錄寫命令。服務器啓動時可以載入AOF文件,通過僞客戶端執行AOF文件命令,還原數據庫狀態。

若服務器AOF持久化功能開啓後,服務器每執行完一個寫命令後,會將該命令以特有的協議格式追加到服務器結構體的aof_buf緩衝區末尾,看下aof_buf的定義:

struct redisServer {
  //……
  //AOF緩衝區
  sds aof_buf;
};

redis服務器進程其實是一個事件循環,這個事件負責接受客戶端的請求。在每次事件循環的末尾,它都會去調用flushAppendOnlyFile函數,這個函數決定是否將aof_buf緩衝區的內容寫入到AOF文件。這個函數的行爲是根據配製文件的appendfsync選項的值來決定的,appendfsync有3個可選項:

appendfsync選項的值 flushAppendOnlyFile函數的行爲
always 將aof_buf中的內容寫入並同步到(寫入磁盤)AOF文件
everysec 每隔一秒將aof_buf中的內容寫入並且同步到AOF文件
no 將aof_buf緩衝區中的所有內容寫入到AOF文件,但是不進行同步操作

這裏解釋下寫入和同步操作:

將數據寫入到文件的時候,操作系統通常會將寫入數據暫存在一個內存緩衝區(注意和aof_buf區分)中,等到內存緩衝區的空間被填滿或者超過一定的時間後纔將緩衝區中的數據真正寫入到磁盤裏面。所以這裏的同步操作指的是將內存緩衝區(不是aof_buf緩衝區)寫入到磁盤文件。

AOF重寫

AOF重寫是通過BGREWRITEAOF命令實現的,AOF持久化有一個問題,隨着服務器的運行,AOF文件會越來越大。redis提供了AOF文件的重寫功能,這個功能可以對一個鍵的一系列寫命令轉化成一條命令。

例如如果對鍵list執行如下一系列命令:

127.0.0.1:6379> rpush list "hello" "world"
(integer) 2
127.0.0.1:6379> rpush list "redis"
(integer) 3
127.0.0.1:6379> lpop list
"hello"

AOF文件將會記錄關於list的這三條命令。AOF文件重寫後list的命令只有一條:

rpush list "world" "redis"

這樣原來關於list的三條命令就可以精簡爲一條命令。

AOF重寫並不是重寫原有的AOF文件,而是通過讀取數據庫的鍵值對來實現的。例如上面的鍵list,重寫是讀取數據庫鍵list對應的鍵值對,並將鍵值對對應的寫命令寫到磁盤文件。

redis的AOF重寫是通過子進程處理的,這樣避免了服務器進程處理AOF重寫造成客戶端的阻塞。

但是這樣帶來了一個問題,子進程在處理AOF重寫時,服務器進程還會繼續處理來自客戶端的請求,這就會改變數據庫的狀態,造成數據庫狀態和新的AOF文件狀態不一致。爲了解決這個問題,redis引入了AOF重寫緩衝區,子進程處理AOF重寫過程中,服務器進程處理客戶端的寫請求時,不僅會將寫命令追加到AOF緩衝區(aof_buf),還會將該寫命令追加到AOF**重寫緩衝區**。這樣可以保證:

  • AOF緩衝區(aof_buf)的內容會被定期寫入和同步到AOF文件,即當前正常流程不受影響。
  • 從創建子進程開始,服務器進程處理的所有寫命令都將會追加到AOF重寫緩衝區。

子進程完成AOF文件重寫後,會發送一個信號給服務器進程,服務器進程執行信號處理函數:

  • 將AOF重寫緩衝區中的所有內容寫入到新的AOF文件中,這樣新的AOF文件和舊的AOF文件狀態就一致了。
  • 將新的AOF文件改名,覆蓋現有的AOF文件,完成新舊文件的替換。

服務器進程收到子進程的信號後,不再響應客戶端的請求,直到完成信號處理函數,服務器進程纔可以正常處理客戶端請求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章