redis持久化機制


      redis是基於內存的數據庫,提供了內存數據持久化到文件的兩種方式,一種是寫RDB文件方式,另一種是寫AOF文件,默認執行的是RDB文件持久化方式。

當在redis.config配置文件中開啟AOF持久化機制時,redis在啟動時,會優先載入AOF文件。其中服務器在載入文件的過程中處於阻塞狀態。下圖為redis啟動時載入持久化文件的流程。

一、RDB文件的創建與載入。

創建RDB文件有兩種命令的方式,Save與BGSAVE,其中Save命令會阻塞redis服務器進程。導致這段期間服務器不能接受客戶端的請求,BGSAVE命令會創建子進程來執行RDB文件的創建。所以BGSAVE不會阻塞服務器進程。SAVE與BGSAVE命令都會以不同的方式來調用rdb.c/rdbSave函數來完成RDB文件的創建工作。偽代碼如下:

        def SAVE():

               rdbSave()//創建RDB文件。

        def BGSAVE():

              pid = fork()//創建子進程。

              if(pid == 0)

                   rdbSave()//子進程負責創建RDB文件。

                   signal_parent()//完成后向父進程發送信號。

              if(pid > 0)

                    handle_request_and_wait_signal()//父進程繼續處理命令請求,並通過輪詢等待子進程的信號。

              else

                    handle_fork_error().

那么在服務器端執行BGSAVE命令期間,如果客戶端發送了SAVE命令,BGSAVE,BGREWRITEAOF 命令時,則服務端拒絕客戶端的命令請求。拒絕save命令是防止父進程與子進程同時執行,rdbSave期間產生競爭條件。BGSAVE同樣是為了防止競爭條件的產生。至於BGREWRITEAOF則是為了性能的考慮,防止兩個進程同時對磁盤進行寫入操作。

二。redis如何同步RDB文件 。

通常情況下,redis通過讀取配置文件定期保存數據庫的狀態到RDB文件。

例如默認配置文件配置如下圖所示:

上圖分別表示在900秒內至少執行1次數據庫修改,300秒內至少10次數據庫修改,60秒內至少10000次數據庫修改操作,只要滿足任何一條,數據庫執行RDB文件的同步操作。

當服務器啟動時會讀取配置文件的以上配置信息,並將上面的配置信息寫入到redisServer結構中的saveparams屬性中。

struct redisServer{

     ......

     long long dirty;//距離最近一次寫入RDB文件所有數據庫文件修改的總次數。

     time_t lastsave;//上一次執行保存RDB文件的時間

  struct saveparam *saveparams;

      .......

}

其中saveparam結構如下:

struct saveparam{

  time_t seconds;//秒數

      int changes;//修改數

}

其中dirty和lastsave的作用就是為了redis 周期性執行函數serverCron(每個100毫秒執行一次)根據saveparams的配置信息檢查條件是否滿足來執行RDB文件的同步操作。下圖為定期執行保存的偽代碼。

 

那么RDB文件保存的數據主要是哪些呢,通過下面的介紹我們將了解到RDB文件保存到數據庫的狀態信息主要是key,value信息。下面我們需要了解RDB文件的組織結構。

三 、RDB文件的結構

RDB文件總體結構如下圖所示:

其中REDIS與EOF 為常量部分。db_version為RDB文件的版本,不同的版本對應的格式會有區別。本文討論的是RDB文件的第六版本。check_sum 主要做RDB文件的校驗和計算,防止文件篡改或者缺損,它的值是根據前面的格式內容進行計算而得。

databases部分分兩種情況,databases為空的情況,即數據庫中沒有數據的情況。和數據庫不為空的情況。

數據庫為空的情況如下圖所示:

數據庫不為空的情況,如下圖所示:

其中database0或database3的 格式的組成如下圖所示:

其中SELECTDB為常量,db_number 為 對應的數據庫。鍵值對為對應的數據庫鍵值,其中包括帶過期時間的鍵值對與不帶過期時間的鍵值對。

不帶過期時間的鍵值對的結構如下圖所示:

其中TYPE記錄了value的類型,值為常量,占一個字節,值為以下常量中的一種。

帶有過期時間的鍵值對的結構如下:

其中:EXPIRETIME_MS表示的是以毫秒為單位的過期時間對應的時間戳。ms表示的是對應的時間值。如 下圖所示:

Redis除了提供RDB文件的持久化方式外,還提供了AOF持久化機制,與RDB保存數據庫的鍵值對的方式不同的是,AOF提供的持久化機制保存的是redis執行的命令 。如下圖所示:

若對redis空數據庫執行如下寫命令:

 

則AOF方式將以redis命令請求協議的方式保存到AOF文件中。對於上面執行的三條命令。AOF文件內容格式如下圖所示。

其中第一行SELECT DB是由redis服務器自動添加上去的。

AOF 文件持久化的實現。

AOF 文件持久化的實現方式分為三個步驟,AOF命令追加,寫AOF文件,同步AOF文件。

其中AOF命令追加是將客戶端請求的命令,以命令協議的方式追加到服務器狀態的redisServer的aof_buf緩沖區的末尾。如下圖所示:

AOF文件的寫入是將追加到AOF緩沖區的命令寫入到AOF文件。這個操作是在文件事件處理程序中來做的。REDIS服務器進程是個事件循環,在接收到客戶端發送的命令請求時,文件事件會從考慮是否將aof_buf緩沖區中的內容寫入和保存到AOF文件。

事件處理函數如下圖所示:

其中flushAppendOnlyFile()函數的行為由配置文件選項:appendfsync 來決定。appendfsync的值為下表中的任何一項,默認appendfsync值為everysec:

其中,這里的寫入表示的是將aof_buf緩沖區的內容寫入到AOF文件,此時這個寫入並沒有真正寫入到磁盤文件,操作系統為了提高文件寫入的效率,在調用write()函數時,會將數據暫且寫入到內存的一塊緩沖區。待緩沖區滿或者強制時間到達或者強制刷新緩沖區時才將數據同步到文件。同步表示的就是將內存緩沖區中的數據同步到磁盤。

通過上面的分析,我們可以看出來在效率與安全性上 得出:

always的安全性 最高,在每個文件時間中都會寫入AOF及同步AOF文件。最多會丟失一個事件循環的命令數據。性能最差,因為每個事件循環都要寫文件及同步文件。

everysec的安全性次之。寫入的性能同no。

最后是no的安全性最弱,會丟失上一次同步開始后的數據,寫入的性能最好,同everysec。

AOF文件的載入與還原過程如下圖所示:

 

AOF重寫

因為AOF文件隨着命令請求的不斷執行,會逐漸變的越來越大。例如如下圖所示:

為了保存list鍵的數據狀態,需要保存6條命令,

其實list的狀態我們可以用一條命令來保存,如:

RPUSH list "C" "D" "E" "F" "G"

其實AOF重寫是保存的是當前數據庫的鍵值對的狀態。它根據值所對應的類型,執行對應的命令操作。 因為在進行AOF命令重寫時會進行大量的寫入操作,這時如果在服務器進程中來進行重寫操作,會阻塞服務器進程,導致其無法處理客戶端的請求,為了避免這種情況的發生,AOF重寫是在子進程中來完成的。子進程會拷貝服務器進程的數據副本,這樣做的好處是保證不再使用鎖的情況下保證數據的安全性。同樣,使用子進程帶來的一個問題是當子進程進行AOF重寫時,服務器進程在這段期間會接收客戶端的命令請求,這導致重寫后的AOF文件與服務器的狀態不一致的情況。為了解決這個情況。redis引入了重寫緩沖區的概念,這個重寫緩沖區在redis創建AOF子進程重寫后使用。重寫期間,命令會被寫入到緩沖區與重寫緩沖區。當子進程完成了AOF文件的重寫,會像父進程發送一個重寫完成的信號,父進程在接收到信號后,會調用信號處理函數,執行以下操作:

1.重寫緩沖區的內容同步到新的AOF文件中。

2.重命名新的AOF文件,覆蓋原有的AOF文件。

此時,信號處理函數再父進程執行,會短暫阻塞服務器進程。

下圖為AOF執行重寫的過程。


免責聲明!

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



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