天天看點

詳解Redis持久化(RDB和AOF)

詳解Redis持久化(RDB和AOF)

什麼是Redis持久化?

Redis讀寫速度快、性能優越是因為它将所有資料存在了記憶體中,然而,當Redis程序退出或重新開機後,所有資料就會丢失。是以我們希望Redis能儲存資料到硬碟中,在Redis服務重新開機之後,原來的資料能夠恢複,這個過程就叫持久化。

Redis持久化的兩種方式?RDB和AOF

AOF:會将每次執行的指令及時儲存到硬碟中,實時性更好,丢失的資料更少

RDB:會根據指定的規則定時将記憶體中的資料儲存到硬碟中。

通常兩種方式結合使用,下面詳細介紹RDB和AOF

AOF

Append Only File,AOF會儲存伺服器執行的所有寫操作到日志檔案中,在服務重新開機以後,會執行這些指令來恢複資料。

日志檔案預設為appendonly.aof,Redis以Redis協定格式将指令儲存至aof日志檔案末尾,aof檔案還會被重寫,使aof檔案的體積不會大于儲存資料集狀态所需要的實際大小

預設情況下,aof沒有被開啟。需要在redis.conf開啟

appendonly yes

日志檔案名

appendfilename "appendonly.aof"

日志檔案所在目錄(RDB日志檔案也是在這裡)

dir ./

fsync持久化政策

appendfsync everysec

always:指令寫入aof_buf後立即調用系統fsync操作同步到AOF檔案,fsync完成後線程傳回。這種情況下,每次有寫指令都要同步到AOF檔案,硬碟IO成為性能瓶頸,Redis隻能支援大約幾百TPS寫入,嚴重降低了Redis的性能;即便是使用固态硬碟(SSD),每秒大約也隻能處理幾萬個指令,而且會大大降低SSD的壽命。

no:指令寫入aof_buf後調用系統write操作,不對AOF檔案做fsync同步;同步由作業系統負責,通常同步周期為30秒。這種情況下,檔案同步的時間不可控,且緩沖區中堆積的資料會很多,資料安全性無法保證。

everysec:指令寫入aof_buf後調用系統write操作,write完成後線程傳回;fsync同步檔案操作由專門的線程每秒調用一次。everysec是前述兩種政策的折中,是性能和資料安全性的平衡,是以是Redis的預設配置,也是我們推薦的配置。

其餘參數:

● no-appendfsync-on-rewrite no:在重寫 AOF 檔案的過程中,是否禁止 fsync。如果這個參數值設定為 yes(開啟),則可以減輕重寫 AOF 檔案時 CPU 和硬碟的負載,但同時可能會丢失重寫 AOF 檔案過程中的資料;需要在負載與安全性之間進行平衡。

● auto-aof-rewrite-percentage 100:指定 Redis 重寫 AOF 檔案的條件,預設為 100,它會對比上次生成的 AOF 檔案大小。如果目前 AOF 檔案的增長量大于上次 AOF 檔案的 100%,就會觸發重寫操作;如果将該選項設定為 0,則不會觸發重寫操作。

● auto-aof-rewrite-min-size 64mb:指定觸發重寫操作的 AOF 檔案的大小,預設為 64MB。如果目前 AOF 檔案的大小低于該值,此時就算目前檔案的增量比例達到了 auto-aof-rewrite-percentage 選項所設定的條件,也不會觸發重寫操作。換句話說,隻有同時滿足以上這兩個選項所設定的條件,才會觸發重寫操作。

● auto-load-truncated yes:當 AOF 檔案結尾遭到損壞時,Redis 在啟動時是否仍加載 AOF 檔案。

AOF持久化的實作

(1)指令追加(append):Redis 伺服器每執行一條寫指令,這條寫指令都會被追加到緩存區 aof_buf 中。(避免每次執行的指令都直接寫入硬碟中,會導緻硬碟 I/O 的負載過大,使得性能下降。)

指令追加的格式使用 Redis 指令請求的協定格式

(2)AOF 持久化檔案寫入(write)和檔案同步(sync):根據 appendfsync 參數設定的不同的同步政策,将緩存區 aof_buf 中的資料内容同步到硬碟中。

先了解作業系統的 write 和 fsync 函數。為了提高檔案的寫入效率,當使用者調用 write 函數将資料寫入檔案中時,作業系統會将這些資料暫存到一個記憶體緩存區中,當這個緩存區被填滿或者超過了指定時限後,才會将緩存區中的資料寫入硬碟中,這樣做既提高了效率,又保證了安全性。

Redis 的伺服器程序是一個事件循環(loop),這個事件循環中的檔案事件負責接收用戶端的指令請求,處理之後,向用戶端發送指令回複;而其中的時間事件則負責執行像 serverCron 函數這樣需要定時運作的函數。

伺服器在處理檔案事件時,可能會執行用戶端發送過來的寫指令,使得一些指令被追加到緩存區 aof_buf 中。是以,在伺服器每次結束一個事件循環之前,都會調用 flushAppendOnlyFile 函數,來決定是否将緩存區 aof_buf 中的資料寫入和儲存到 AOF 檔案中。

flushAppendOnlyFile 函數的運作與伺服器配置的 appendfsync 參數有關。

AOF檔案的重寫

AOF檔案定期重寫,以達到壓縮的目的。

AOF檔案過于龐大,會影響Redis的寫入速度,在執行資料恢複時,也非常滿。AOF檔案重寫就是解決這個問題。

AOF 檔案重寫就是把 Redis 程序内的資料轉化為寫指令,然後同步到新的 AOF 檔案中。在重寫的過程中,Redis 伺服器會建立一個新的 AOF 檔案來替代現有的 AOF 檔案,新、舊兩個 AOF 檔案所儲存的資料庫狀态相同,但是新的 AOF 檔案不會包含備援指令。

AOF 檔案重寫并不會對舊的 AOF 檔案進行讀取、寫入操作,這個功能是通過讀取伺服器目前的資料庫狀态來實作的。

舉例說明:

​ 比如Redis使用五條rpush指令分别插入五種顔色

rpush color 〝yellow〝

rpush color 〝green〝

...

​ AOF重寫後

RPUSH color 〝yellow〝 〝green〝 〝black〝 〝pink〝〝white〝

是以AOF重寫的原理:

● AOF 檔案重寫功能會丢棄過期的資料,也就是過期的資料不會被寫入 AOF 檔案中。

● AOF 檔案重寫功能會丢棄無效的指令,無效的指令将不會被寫入 AOF 檔案中。無效指令包括重複設定某個鍵值對時的指令、删除某些資料時的指令等。

● AOF 檔案重寫功能可以将多條指令合并為一條指令,然後寫入 AOF 檔案中。

怎麼觸發AOF重寫?

● 手動觸發:執行 BGREWRITEAOF 指令觸發 AOF 檔案重寫。該指令與 BGSAVE 指令相似,都是啟動(fork)子程序完成具體的工作,且都在啟動時阻塞。

● 自動觸發:自動觸發 AOF 檔案重寫是通過設定 Redis 配置檔案中 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 參數的值,以及 aof_current_size 和 aof_base_size 狀态來确定何時觸發的。

auto-aof-rewrite-percentage 參數是在執行 AOF 檔案重寫時,目前 AOF 檔案的大小(aof_current_size)和上一次 AOF 檔案重寫時的大小(aof_base_size)的比值,預設為 100。

auto-aof-rewrite-min-size 參數設定了執行 AOF 檔案重寫時的最小體積,預設為 64MB。

AOF檔案的背景重寫:

AOF重寫執行大量的寫入操作,就會使得這個函數的線程被長時間阻塞。Redis 伺服器使用單線程來處理指令請求。如果讓伺服器直接調用 aof_rewrite 重寫函數,那麼在 AOF 檔案重寫期間,伺服器将不能繼續執行其他指令,就會一直處于阻塞狀态。

Redis 将 AOF 檔案重寫程式放到了一個子程序中執行,這樣做的好處是:

● 子程序在執行 AOF 檔案重寫的過程中,Redis 伺服器程序可以繼續處理新的指令請求。

● 子程序帶有伺服器程序的資料副本,使用子程序可以在使用鎖的情況下,保證資料的安全性。

使用子程序會導緻資料庫狀态不一緻,原因是:當子程序進行 AOF 檔案重寫的時候,Redis 伺服器可以繼續執行來自用戶端的指令請求,就會有新的指令對現有資料庫狀态進行修改,進而使得伺服器目前的資料庫狀态與重寫的 AOF 檔案所儲存的資料庫狀态不一緻。

為了解決使用子程序導緻資料庫狀态不一緻的問題,Redis 伺服器設定了一個 AOF 檔案重寫緩存區。這個 AOF 檔案重寫緩存區在伺服器建立子程序之後開始使用,可以利用它來解決資料庫狀态不一緻的問題。當 Redis 伺服器成功執行完一條寫指令後,它會同時将這條寫指令發送給 AOF 檔案緩存區(aof_buf)和 AOF 檔案重寫緩存區。

子程序在執行 AOF 檔案重寫的過程中,伺服器程序的執行過程如下:

(1)伺服器接收到來自用戶端的指令請求,并成功執行。

(2)伺服器将執行後的寫指令轉化為對應的協定格式,然後追加到 AOF 檔案緩存區(aof_buf)中。

(3)伺服器再将執行後的寫指令追加到 AOF 檔案重寫緩存區中。

有了 AOF 檔案重寫緩存區,就可以保證資料庫狀态的一緻性。AOF 檔案緩存區的内容會被定期寫入和同步到 AOF 檔案中,AOF 檔案的寫入和同步不會因為 AOF 檔案重寫緩存區的引入而受到影響。當伺服器建立子程序之後,伺服器執行的所有寫指令都會同時被追加到 AOF 檔案緩存區和 AOF 檔案重寫緩存區中。

如果子程序完成了 AOF 檔案重寫的工作,它就會發送一個完成信号給父程序。當父程序接收到這個信号後,就會調用信号處理函數,繼續執行以下工作:

(1)将 AOF 檔案重寫緩存區中的所有内容寫入新的 AOF 檔案中。新的 AOF 檔案所儲存的資料庫狀态與伺服器目前的資料庫狀态保持一緻。

(2)修改新的 AOF 檔案的檔案名,新生成的 AOF 檔案将會覆寫現有(舊)的 AOF 檔案,完成新、舊兩個檔案的互換。

在完成上述兩個步驟之後,就完成了一次 AOF 檔案背景重寫工作。

在整個 AOF 檔案背景重寫的過程中,隻有在信号處理函數執行的過程中,伺服器程序才會被阻塞,在其他時候不存在阻塞情況。

AOF檔案恢複資料的過程

(1)建立一個僞用戶端,用于執行 AOF 檔案中的寫指令。這個僞用戶端是一個不帶網絡連接配接的用戶端。因為隻能在用戶端的上下文中才能執行 Redis 的指令,而在 AOF 檔案中包含了 Redis 伺服器啟動加載 AOF 檔案時所使用的所有指令,而不是網絡連接配接,是以伺服器建立了一個不帶網絡連接配接的僞用戶端來執行 AOF 檔案中的寫指令。

(2)讀取 AOF 檔案中的資料,分析并提取出 AOF 檔案所儲存的一條寫指令。

(3)使用僞用戶端執行被讀取出的寫指令。

(4)重複執行步驟(2)和(3),直到将 AOF 檔案中的所有指令讀取完畢,并成功執行為止。這個過程完成之後,就可以将伺服器的資料庫狀态還原為關閉之前的狀态。

如果在 Redis 伺服器啟動加載 AOF 檔案時,發現 AOF 檔案被損壞了,那麼伺服器會拒絕加載這個 AOF 檔案,以此來確定資料的一緻性不被破壞。而 AOF 檔案被損壞的原因可能是程式正在對 AOF 檔案進行寫入與同步時,伺服器出現停機故障。如果 AOF 檔案被損壞了,則可以通過以下方法來修複。

● 及時備份現有 AOF 檔案。

● 利用 Redis 自帶的 redis-check-aof 程式,對原來的 AOF 檔案進行修複,指令如下:

● 使用 diff-u 來對比原始 AOF 檔案和修複後的 AOF 檔案,找出這兩個檔案的不同之處。

● 修複 AOF 檔案之後,重新開機 Redis 伺服器重新加載,進行資料恢複。

AOF持久化的優劣

● 使用 AOF 持久化會讓 Redis 持久化更長:通過設定不同的 fsync 政策來達到更長的持久化。具體有 3 種政策。

● 相容性比較好:AOF 檔案是一個日志檔案,它的作用是記錄伺服器執行的所有寫指令。當檔案因為某條寫指令寫入失敗時,可以使用 redis-check-aof 進行修複,然後繼續使用。

● 支援背景重寫:當 AOF 檔案的體積過大時,在背景可以自動地對 AOF 檔案進行重寫,是以資料庫目前狀态的所有指令集合都會被重寫到 AOF 檔案中。重寫完成後,Redis 就會切換到新的 AOF 檔案,繼續執行寫指令的追加操作。

● AOF 檔案易于讀取和加載:AOF 檔案儲存了對資料庫的所有寫指令,這些寫指令采用 Redis 協定格式追加到 AOF 檔案中,是以非常容易讀取和加載。

AOF 持久化具有以下缺點:

● AOF 檔案的體積會随着時間的推移逐漸變大,導緻在加載時速度會比較慢,進而影響資料庫狀态的恢複速度,性能快速下降。

● 根據所使用的 fsync 政策,使用 AOF 檔案恢複資料的速度可能會慢于使用 RDB 檔案恢複資料的速度。

● 因為 AOF 檔案的個别指令,可能會導緻在加載時失敗,進而無法進行資料恢複。

RDB

RDB 持久化生成的 RDB 檔案是一個經過壓縮的二進制檔案,也可以稱之為快照檔案,通過該檔案可以還原生成 RDB 檔案時的資料庫狀态

在指定的時間間隔内,Redis 會自動将記憶體中的所有資料生成一份副本并存儲在硬碟上,這個過程就是「快照」。

快照怎麼生成

● 根據 Redis 配置檔案 redis.conf 中的配置自動進行快照

save 900 1

save 300 10

save 60 10000

save m n //時間 m 和被修改的鍵的個數 n

當在時間 m 内被修改的鍵的個數大于 n 時,就會觸發 BGSAVE 指令,伺服器就會自動執行快照操作。

Redis 的 save m n 指令是通過 serverCron 函數、dirty 計數器及 lastsave 時間戳來實作的。

serverCron 函數:這是 Redis 伺服器的周期性操作函數,預設每隔 100 毫秒執行一次,它主要的作用是維護伺服器的狀态。其中一項工作就是判斷 save m n 配置的條件是否滿足,如果滿足就會觸發執行 BGSAVE 指令。

dirty 計數器:這是 Redis 伺服器維持的一種狀态,它主要用于記錄上一次執行 SAVE 或 BGSAVE 指令後,伺服器進行了多少次狀态修改(執行添加、删除、修改等操作);當 SAVE 或 BGSAVE 指令執行完成後,伺服器會将 dirty 重新設定為 0。dirty 計數器記錄的是伺服器進行了多少次狀态修改,而不是用戶端執行了多少次修改資料的指令。

lastsave 時間戳:主要用于記錄伺服器上一次成功執行 SAVE 或 BGSAVE 指令的時間,它是 Redis 伺服器維持的一種狀态。

dirty 計數器和 lastsave 時間戳屬性都儲存在伺服器狀态的 redisServer 結構中。

save m n 指令的實作原理:伺服器每隔 100 毫秒執行一次 serverCron 函數;serverCron 函數會周遊 save m n 配置的儲存條件,判斷是否滿足。如果有一個條件滿足,就會觸發執行 BGSAVE 指令,進行快照儲存。

對于每個 save m n 條件,隻有以下兩個條件同時滿足才算滿足:

➢ 目前伺服器時間減去 lastsave 時間戳大于 m。

➢ 目前 dirty 計數器的個數大于等于 n。

● 使用者在用戶端執行 SAVE 或 BGSAVE 指令時會觸發快照(手動觸發)。

● 如果使用者定義了自動快照條件,則執行 FLUSHALL 指令也會觸發快照。

當執行 FLUSHALL 指令時,會清空資料庫中的所有資料。如果使用者定義了自動快照條件,則在使用 FLUSHALL 指令清空資料庫的過程中,就會觸發伺服器執行一次快照。

● 如果使用者為 Redis 設定了主從複制模式,從節點執行全量複制操作,則主節點會執行 BGSAVE 指令,将生産的 RDB 檔案發送給從節點完成快照操作。

快照的實作過程

(1)Redis 調用執行 fork 函數複制一份目前程序(父程序)的副本(子程序),也就是同時擁有父程序和子程序。

(2)父程序與子程序各自分工,父程序繼續處理來自用戶端的指令請求,而子程序則将記憶體中的資料寫到硬碟上的一個臨時 RDB 檔案中。

(3)當子程序把所有資料寫完後,也就表示快照生成完畢,此時舊的 RDB 檔案将會被這個臨時 RDB 檔案替換,這個舊的 RDB 檔案也會被删除。這個過程就是一次快照的實作過程。

當 Redis 調用執行 fork 函數時,作業系統會使用寫時複制政策。也就是在執行 fork 函數的過程中,父、子程序共享同一記憶體資料,當父程序要修改某個資料時(執行一條寫指令),作業系統會将這個共享記憶體資料另外複制一份給子程序使用,以此來保證子程序的正确運作。是以,新的 RDB 檔案存儲的是執行 fork 函數過程中的記憶體資料。

寫時複制政策也保證了在執行 fork 函數的過程中生成的兩份記憶體副本在記憶體中的占用量不會增加一倍。但是,在進行快照的過程中,如果寫操作比較多,就會造成 fork 函數執行前後資料差異較大,此時會使得記憶體使用量變大。因為記憶體中不僅儲存了目前資料庫資料,還會儲存 fork 過程中的記憶體資料。

在進行快照生成的過程中,Redis 不會修改 RDB 檔案。隻有當快照生成後,舊的 RDB 檔案才會被臨時 RDB 檔案替換,同時舊的 RDB 檔案會被删除。在整個過程中,RDB 檔案是完整的,是以我們可以使用 RDB 檔案來實作 Redis 資料庫的備份。

RDB 檔案

預設情況下,Redis 将資料庫快照儲存在名為 dump.rdb 的檔案中。這個檔案可以修改,即AOF的dir和dbfilename 屬性

RDB 檔案結構:

在 RDB 檔案結構中,通常使用大寫字母表示常量,使用小寫字母表示變量和資料。

● REDIS 常量:該常量位于 RDB 檔案的頭部,它儲存着「REDIS」5 個字元,它的長度是 5 位元組。在 Redis 伺服器啟動加載檔案時,程式會根據這 5 個字元判斷加載的檔案是不是 RDB 檔案。

● db_version 常量:該常量用于記錄 RDB 檔案的版本号,它的值是一個用字元串表示的整數,占 4 位元組,注意區分它不是 Redis 的版本号。

● databases 資料:它包含 0 個或多個資料庫,以及各個資料庫中的鍵值對資料。

如果它包含 0 個資料庫,也就是伺服器的資料庫狀态為空,那麼 databases 也是空的,其長度為 0 位元組;如果它包含多個資料庫,也就是伺服器的資料庫狀态不為空,那麼 databases 不為空,根據它所儲存的鍵值對的數量、類型和内容不同,其長度也是不一樣的。

如果 databases 不為空,則 RDB 檔案結構如圖

其中,SELECTDB 是一個常量,表示其後的資料庫編号,這裡的 0 和 1 是資料庫編号。

pairs 資料:它存儲了具體的鍵值對資訊,包括鍵(key)、值(value)、資料類型、内部編碼、過期資訊、壓縮資訊等。

SELECT 0 pairs 表示 0 号資料庫;SELECT 1 pairs 表示 1 号資料庫。當資料庫中有鍵值對時,RDB 檔案才會記錄該資料庫的資訊;而如果資料庫中沒有鍵值對,這一部分就會被 RDB 檔案省略。

● EOF 常量:該常量是一個結束标志,它标志着 RDB 檔案的正文内容結束,其長度為 1 位元組。在加載 RDB 檔案時,如果遇到 EOF 常量,則表示資料庫中的所有鍵值對都已經加載完畢。

● check_sum 變量:該變量用于儲存一個校驗和,這個校驗和是通過對 REDIS、db_version、databases、EOF 4 部分的内容進行計算得出的,是一個無符号整數,其長度為 8 位元組。

當伺服器加載 RDB 檔案時,會将 check_sum 變量中儲存的校驗和與加載資料時所計算出來的校驗和進行比對,進而判斷加載的 RDB 檔案是否被損壞,或者是否有錯誤。

RDB檔案壓縮

在預設情況下,Redis 伺服器會自動對 RDB 檔案進行壓縮。在 Redis 配置檔案 redis.conf 中,預設開啟壓縮。配置如下:

Redis 采用 LZF 算法進行 RDB 檔案壓縮。在壓縮 RDB 檔案時,不要誤認為是壓縮整個 RDB 檔案。實際上,對 RDB 檔案的壓縮隻是針對資料庫中的字元串進行的,并且隻有當字元串達到一定長度(20 位元組)時才會進行壓縮。

RDB檔案的建立

SAVE 指令: 會阻塞 Redis 伺服器程序,此時 Redis 伺服器将不能繼續執行其他指令請求,直到 RDB 檔案建立完畢為止。

BGSAVE 指令: 會派生出一個子程序,交由子程序将記憶體中的資料儲存到硬碟中,建立 RDB 檔案;而 BGSAVE 指令的父程序可以繼續處理來自用戶端的指令請求。

執行 BGSAVE 指令會傳回 Background saving started 資訊,但我們并不能确定 BGSAVE 指令是否已經成功執行,此時可以使用 LASTSAVE 指令來檢視相關資訊。

執行 LASTSAVE 指令傳回一個 UNIX 格式的時間戳,表示最近一次 Redis 成功将資料儲存到硬碟中的時間。

RDB檔案的加載

RDB 檔案隻有在啟動伺服器的時候才會被加載。當啟動伺服器時,它會檢查 RDB 檔案是否存在,如果存在,就會自動加載 RDB 檔案。除此之外,RDB 檔案不會被加載,因為 Redis 中沒有提供用于加載 RDB 檔案的指令。

那麼先加載AOF檔案還是RDB檔案呢?

● 如果在 Redis 配置檔案中開啟了 AOF 持久化(appendonly yes),那麼在啟動伺服器的時候會優先加載 AOF 檔案來還原資料庫狀态。

● 如果在 Redis 配置檔案中關閉了 AOF 持久化(appendonly no),那麼在啟動伺服器的時候會優先加載 RDB 檔案來還原資料庫狀态。

RDB檔案的配置

除了save m n、 dbfilename dump.rdb、 dir./ 還有

● stop-writes-on-bgsave-error yes:當執行 BGSAVE 指令出現錯誤時,Redis 是否終止執行寫指令。參數的值預設被設定為 yes,表示當硬碟出現問題時,伺服器可以及時發現,及時避免大量資料丢失;當設定為 no 時,就算執行 BGSAVE 指令發生錯誤,伺服器也會繼續執行寫指令;當對 Redis 伺服器的系統設定了監控時,建議将該參數值設定為 no。

● rdbcompression yes:是否開啟 RDB 壓縮檔案,預設為 yes 表示開啟,不開啟則設定為 no。

● rdbchecksum yes:是否開啟 RDB 檔案的校驗,在伺服器進行 RDB 檔案的寫入與讀取時會用到它。預設設定為 yes。如果将它設定為 no,則在伺服器對 RDB 檔案進行寫入與讀取時,可以提升性能,但是無法确定 RDB 檔案是否已經被損壞。

RDB 持久化的優劣

● RDB 檔案是一個經過壓縮的二進制檔案,檔案緊湊,體積較小,非常适用于進行資料庫資料備份。

● RDB 持久化适用于災難恢複,而且恢複資料時的速度要快于 AOF 持久化。

● Redis 采用 RDB 持久化可以很大程度地提升性能。父程序在儲存 RDB 檔案時會啟動一個子程序,将所有與儲存相關的功能交由子程序處理,而父程序可以繼續處理其他相關的操作。

RDB 持久化具有以下缺點:

● 在伺服器出現故障時,如果沒有觸發 RDB 快照執行,那麼它可能會丢失大量資料。RDB 快照的持久化方式決定了必然做不到實時持久化,會存在大量資料丢失。

● 當資料量非常龐大時,在儲存 RDB 檔案的時候,伺服器會啟動一個子程序來完成相關的儲存操作。這項操作比較耗時,将會占用太多 CPU 時間,進而影響伺服器的性能。

● RDB 檔案存在相容性問題,老版本的 Redis 不支援新版本的 RDB 檔案。

原文位址

https://www.cnblogs.com/undefined22/p/12619546.html