Redis數據持久化
持久化選項
Redis提供了兩種不同的持久化方法來將數據存儲到硬盤裏面。一種方法叫快照(snapshotting),它可以將存在於某一時刻的所有數據都寫入硬盤裏面。另一種方法叫只追加文件(append-onlyfile,AOF),它會在執行寫命令時,將被執行的寫命令復制到硬盤裏面。這兩種持久化方法既可以同時使用,又可以單獨使用,在某些情況下甚至可以兩種方法都不使用,具體選擇哪種持久化方法需要根據用戶的數據以及應用來決定。
將內存中的數據存儲到硬盤的一個主要原因是為了在之後重用數據,或者是為了防止系統故障而將數據備份到一個遠程位置。另外,存儲在Redis裏面的數據有可能是經過長時間計算得出的,或者有程序正在使用Redis存儲的數據進行計算,所以用戶會希望自己可以將這些數據存儲起來以便之後使用,這樣就不必再重新計算了。對於一些Redis應用來說,“計算”可能只是簡單地將另一個數據庫的數據復制到Redis裏面,但對於另外一些Redis應用來說,Redis存儲的數據可能是根據數十億行日誌進行聚合分析得出的結果。
兩組不同的配置選項控制著Redis將數據寫入硬盤裏面的方式,代碼清單4-1展示了這些配置選項以及它們的示例配置值。
代碼清單4-1最開頭的幾個選項和快照持久化有關,比如:如何命名硬盤上的快照文件、多久執行一次自動快照操作、是否對快照文件進行壓縮,以及在創建快照失敗後是否仍然繼續執行寫命令。代碼清單的第二組選項用於配置AOF子系統(subsystem):這些選項告訴Redis是否使用AOF持久化、多久才將寫入的內容同步到硬盤、在對AOF進行壓縮(compaction)的時候能否執行同步操作,以及多久執行一次AOF壓縮。
快照持久化
Redis 可以通過創建快照來獲得存儲在內存裏面的數據在某個時間點上的副本。在創建快照 之後,用戶可以對快照進行備份,可以將快照復制到其他服務器從而創建具有相同數據的服務器副本,還可以將快照留在原地以便重啟服務器時使用。
根據配置,快照將被寫入dbfilename選項指定的文件裏面,並儲存在dir選項指定的路徑上面。如果在新的快照文件創建完畢之前,Redis、系統或者硬件這三者之中的任意一個崩潰了,那麽Redis將丟失最近一次創建快照之後寫入的所有數據。
舉個例子,假設Redis目前在內存裏面存儲了10GB的數據,上一個快照是在下午2:35開始創建的,並且已經創建成功。下午3:06時,Redis又開始創建新的快照,並且在下午3:08快照文件創建完畢之前,有35個鍵進行了更新。如果在下午3:06至下午3:08期間,系統發生崩潰,導致Redis無法完成新快照的創建工作,那麽Redis將丟失下午2:35之後寫入的所有數據。另一方面,如果系統恰好在新的快照文件創建完畢之後崩潰,那麽Redis將只丟失35個鍵的更新數據。
創建快照的辦法有以下幾種。
- 客戶端可以通過向Redis發送BGSAVE命令來創建一個快照。對於支持BGSAVE命令的平臺來說(基本上所有平臺都支持,除了Windows平臺),Redis會調用fork來創建一個子進程,然後子進程負責將快照寫入硬盤,而父進程則繼續處理命令請求。
- 客戶端還可以通過向Redis發送SAVE命令來創建一個快照,接到SAVE命令的Redis服務器在快照創建完畢之前將不再響應任何其他命令。SAVE命令並不常用,我們通常只會在沒有足夠內存去執行BGSAVE命令的情況下,又或者即使等待持久化操作執行完畢也無所謂的情況下,才會使用這個命令。
-
- 如果用戶設置了save配置選項,比如save 60 10000,那麽從Redis最近一次創建快照之後開始算起,當“60秒之內有10000次寫入”這個條件被滿足時,Redis就會自動觸發BGSAVE命令。如果用戶設置了多個save配置選項,那麽當任意一個save配置選項所設置的條件被滿足時,Redis就會觸發一次BGSAVE命令。
-
- 當Redis通過SHUTDOWN命令接收到關閉服務器的請求時,或者接收到標準TERM信號時,會執行一個SAVE命令,阻塞所有客戶端,不再執行客戶端發送的任何命令,並在SAVE命令執行完畢之後關閉服務器。
- 當一個Redis服務器連接另一個Redis服務器,並向對方發送SYNC命令來開始一次復制操作的時候,如果主服務器目前沒有在執行BGSAVE操作,或者主服務器並非剛剛執行完BGSAVE操作,那麽主服務器就會執行BGSAVE命令。
在只使用快照持久化來保存數據時,一定要記住:如果系統真的發生崩潰,用戶將丟失最近一次生成快照之後更改的所有數據。因此,快照持久化只適用於那些即使丟失一部分數據也不會造成問題的應用程序,而不能接受這種數據損失的應用程序則可以考慮使用AOF持久化。
快照持久化的場景
接下來將展示幾個使用快照持久化的場景,大家可以從中學習到如何通過修改配置來獲得自己想要的快照持久化行為。
1.個人開發
在個人開發服務器上面,主要考慮的是盡可能地降低快照持久化帶來的資源消耗。基於這個原因以及對自己硬件的信任,我只設置了save 90 01這一條規則。其中save選項告知Redis,它應該根據這個選項提供的兩個值來執行BGSAVE操作。在這個規則設置下,如果服務器距離上次成功生成快照已經超過了900秒(也就是15分鐘),並且在此期間執行了至少一次寫入操作,那麽Redis就會自動開始一次新的BGSAVE操作。
如果你打算在生產服務器中使用快照持久化並存儲大量數據,那麽你的開發服務器最好能夠運行在與生產服務器相同或者相似的硬件上面,並在這兩個服務器上使用相同的save選項、存儲相似的數據集並處理相近的負載量。把開發環境設置得盡量貼近生產環境,有助於判斷快照是否生成得過於頻繁或者過於稀少(過於頻繁會浪費資源,而過於稀少則帶有丟失大量數據的隱患)。
2.大數據
當Redis存儲的數據量只有幾個GB的時候,使用快照來保存數據是沒有問題的。Redis會創建子進程並將數據保存到硬盤裏面,生成快照所需的時間比你讀這句話所需的時間還要短。但隨著Redis占用的內存越來越多,BGSAVE在創建子進程時耗費的時間也會越來越多。如果Redis的內存占用量達到幾十個GB,並且剩余的空閑內存並不多,或者Redis運行在虛擬機(virtual machine)上面,那麽執行BGSAVE可能會導致系統長時間地停頓,也可能引發系統大量地使用虛擬內存(virtual memory),從而導致Redis的性能降低至無法使用的程度。
執行BGSAVE而導致的停頓時間有多長取決於Redis所在的系統:對於真實的硬件、VMWare虛擬機或者KVM虛擬機來說,Redis進程每占用一個GB的內存,創建該進程的子進程所需的時間就要增加10~20毫秒;而對於Xen虛擬機來說,根據配置的不同,Redis進程每占用一個GB的內存,創建該進程的子進程所需的時間就要增加200~300毫秒。因此,如果我們的Redis進程占用了20GB的內存,那麽在標準硬件上運行BGSAVE所創建的子進程將導致Redis停頓200~400毫秒;如果我們使用的是Xen虛擬機(亞馬遜EC2和其他幾個雲計算供應商都使用這種虛擬機),那麽相同的創建子進程操作將導致Redis停頓4~6秒。用戶必須考慮自己的應用程序能否接受這種停頓。
為了防止Redis因為創建子進程而出現停頓,我們可以考慮關閉自動保存,轉而通過手動發送BGSAVE或者SAVE來進行持久化。手動發送BGSAVE一樣會引起停頓,唯一不同的是用戶可以通過手動發送BGSAVE命令來控制停頓出現的時間。另一方面,雖然SAVE會一直阻塞Redis直到快照生成完畢,但是因為它不需要創建子進程,所以就不會像BGSAVE一樣因為創建子進程而導致Redis停頓;並且因為沒有子進程在爭搶資源,所以SAVE創建快照的速度會比BGSAVE創建快照的速度要來得更快一些。
根據個人經驗,在一臺擁有68GB內存的Xen虛擬機上面,對一個占用50GB內存的Redis服務器執行BGSAVE命令的話,光是創建子進程就需要花費15秒以上,而生成快照則需要花費15~20分鐘;但使用SAVE只需要3~5分鐘就可以完成快照的生成工作。因為我的應用程序只需要每天生成一次快照,所以我寫了一個腳本,讓它在每天淩晨3點停止所有客戶端對Redis的訪問,調用SAVE命令並等待該命令執行完畢,之後備份剛剛生成的快照文件,並通知客戶端繼續執行操作。
如果用戶能夠妥善地處理快照持久化可能會帶來的大量數據丟失,那麽快照持久化對用戶來說將是一個不錯的選擇,但對於很多應用程序來說,丟失15分鐘、1小時甚至更長時間的數據都是不可接受的,在這種情況下,我們可以使用AOF持久化來將存儲在內存裏面的數據盡快地保存到硬盤裏面。
AOF持久化
簡單來說,AOF持久化會將被執行的寫命令寫到AOF文件的末尾,以此來記錄數據發生的變化。因此,Redis只要從頭到尾重新執行一次AOF文件包含的所有寫命令,就可以恢復AOF文件所記錄的數據集。AOF持久化可以通過設置代碼清單4-1所示的appendonly yes配置選項來打開。表4-1展示了appendfsync配置選項對AOF文件的同步頻率的影響。
文件同步 在向硬盤寫入文件時,至少會發生3件事。當調用file.write()方法(或者其他編程語言裏面的類似操作)對文件進行寫入時,寫入的內容首先會被存儲到緩沖區,然後操作系統會在將來的某個時候將緩沖區存儲的內容寫入硬盤,而數據只有在被寫入硬盤之後,才算是真正地保存到了硬盤裏面。用戶可以通過調用file.flush()方法來請求操作系統盡快地將緩沖區存儲的數據寫入硬盤裏,但具體何時執行寫入操作仍然由操作系統決定。除此之外,用戶還可以命令操作系統將文件同步(sync)到硬盤,同步操作會一直阻塞直到指定的文件被寫入硬盤為止。當同步操作執行完畢之後,即使系統出現故障也不會對被同步的文件造成任何影響。
如果用戶使用appendfsync always選項的話,那麽每個Redis寫命令都會被寫入硬盤,從而將發生系統崩潰時出現的數據丟失減到最少。不過遺憾的是,因為這種同步策略需要對硬盤進行大量寫入,所以Redis處理命令的速度會受到硬盤性能的限制:轉盤式硬盤(spinning disk)在這種同步頻率下每秒只能處理大約200個寫命令,而固態硬盤(solid-state drive,SSD)每秒大概也只能處理幾萬個寫命令。
警告:固態硬盤和appendfsync always使用:固態硬盤的用戶請謹慎使用appendfsync always選項,因為這個選項讓Redis每次只寫入一個命令,而不是像其他appendfsync選項那樣一次寫入多個命令,這種不斷地寫入少量數據的做法有可能會引發嚴重的寫入放大(write amplification)問題,在某些情況下甚至會將固態硬盤的壽命從原來的幾年降低為幾個月。
為了兼顧數據安全和寫入性能,用戶可以考慮使用appendfsynceverysec選項,讓Redis以每秒一次的頻率對AOF文件進行同步。Redis每秒同步一次AOF文件時的性能和不使用任何持久化特性時的性能相差無幾,而通過每秒同步一次AOF文件,Redis可以保證,即使出現系統崩潰,用戶也最多只會丟失一秒之內產生的數據。當硬盤忙於執行寫入操作的時候,Redis還會優雅地放慢自己的速度以便適應硬盤的最大寫入速度。
最後,如果用戶使用appendfsync no選項,那麽Redis將不對AOF文件執行任何顯式的同步操作,而是由操作系統來決定應該在何時對AOF文件進行同步。這個選項在一般情況下不會對Redis的性能帶來影響,但系統崩潰將導致使用這種選項的Redis服務器丟失不定數量的數據。另外,如果用戶的硬盤處理寫入操作的速度不夠快的話,那麽當緩沖區被等待寫入硬盤的數據填滿時,Redis的寫入操作將被阻塞,並導致Redis處理命令請求的速度變慢。因為這個原因,一般來說並不推薦使用appendfsync no選項,在這裏介紹它只是為了完整列舉appendfsync選項可用的3個值。
雖然AOF持久化非常靈活地提供了多種不同的選項來滿足不同應用程序對數據安全的不同要求,但AOF持久化也有缺陷—那就是AOF文件的體積大小。
重寫/壓縮AOF文件
在閱讀了上一節對AOF持久化的介紹之後,讀者可能會感到疑惑:AOF持久化既可以將丟失數據的時間窗口降低至1秒(甚至不丟失任何數據),又可以在極短的時間內完成定期的持久化操作,那麽我們有什麽理由不使用AOF持久化呢?但是這個問題實際上並沒有那麽簡單,因為Redis會不斷地將被執行的寫命令記錄到AOF文件裏面,所以隨著Redis不斷運行,AOF文件的體積也會不斷增長,在極端情況下,體積不斷增大的AOF文件甚至可能會用完硬盤的所有可用空間。還有另一個問題就是,因為Redis在重啟之後需要通過重新執行AOF文件記錄的所有寫命令來還原數據集,所以如果AOF文件的體積非常大,那麽還原操作執行的時間就可能會非常長。
為了解決AOF文件體積不斷增大的問題,用戶可以向Redis發送BGREWRITEAOF命令,這個命令會通過移除AOF文件中的冗余命令來重寫(rewrite)AOF文件,使AOF文件的體積變得盡可能地小。BGREWRITEAOF的工作原理和BGSAVE創建快照的工作原理非常相似:Redis會創建一個子進程,然後由子進程負責對AOF文件進行重寫。因為AOF文件重寫也需要用到子進程,所以快照持久化因為創建子進程而導致的性能問題和內存占用問題,在AOF持久化中也同樣存在。更糟糕的是,如果不加以控制的話,AOF文件的體積可能會比快照文件的體積大好幾倍,在進行AOF重寫並刪除舊AOF文件的時候,刪除一個體積達到數十GB大的舊AOF文件可能會導致操作系統掛起(hang)數秒。
跟快照持久化可以通過設置save選項來自動執行BGSAVE一樣,AOF持久化也可以通過設置auto-aof-rewrite-percentage選項和auto-aof-rewrite-min-size選項來自動執行BGREWRITEAOF。舉個例子,假設用戶對Redis設置了配置選項auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb,並且啟用了AOF持久化,那麽當AOF文件的體積大於64MB,並且AOF文件的體積比上一次重寫之後的體積大了至少一倍(100%)的時候,Redis將執行BGREWRITEAOF命令。如果AOF重寫執行得過於頻繁的話,用戶可以考慮將auto-aof-rewrite-percentage選項的值設置為100以上,這種做法可以讓Redis在AOF文件的體積變得更大之後才執行重寫操作,不過也會讓Redis在啟動時還原數據集所需的時間變得更長。
無論是使用AOF持久化還是快照持久化,將數據持久化到硬盤上都是非常有必要的,但除了進行持久化之外,用戶還必須對持久化所得的文件進行備份(最好是備份到多個不同的地方),這樣才能盡量避免數據丟失事故發生。如果條件允許的話,最好能將快照文件和最新重寫的AOF文件備份到不同的服務器上面。
通過使用AOF持久化或者快照持久化,用戶可以在系統重啟或者崩潰的情況下仍然保留數據。隨著負載量的上升,或者數據的完整性變得越來越重要時,用戶可能需要使用復制特性。
參考資料
黃健宏:<Redis實戰>
Redis數據持久化