redis支持將內存中的數據週期性的寫入磁盤或者把操做追加到記錄文件中,這個過程稱爲redis的持久化。redis支持兩種方式的持久化,一種是快照方式(snapshotting),也稱RDB方式;兩一種是追加文件方式(append-only file),也稱AOF方式。RDB方式是redis默認的持久化方式。持久化能夠避免因進程退出而形成數據丟失。html
Redis持久化文件加載流程ios
RDB持久化是把當前進程數據以二進制的方式生成快照(.rdb)文件保存到硬盤的過程,有手動觸發和自動觸發,手動觸發有兩個命令來生成RDB文件,一個是SAVE,另外一個是BGSAVE。redis
SAVE命令:阻塞Redis服務器進程,在阻塞期間,服務器不能處理任何命令請求,直到RDB文件建立完畢爲止,。數據庫
BGSAVE命令:Redis進程執行fork操做建立子線程,由子線程負責建立RDB文件完成持久化,服務器進程(父進程)繼續處理命令請求。當BGSAVE命令在執行期間,客戶端再發送BGSAVE命令會被服務器拒絕,由於同時執行兩個GBSAVE命令也會產生競爭條件。最後BGREWRITEAOF和GBSAVE兩個命令也不能同時執行。若是沒有開啓AOF持久化,自動執行BGSAVE;顯然BGSAVE是對SAVE的優化。下圖是SAVE命令和BGSAVE命令的對比:
緩存
命令 | SAVE | BGSAVE |
IO類型 | 同步 | 異步 |
是否阻塞 | 阻塞 | 非阻塞(在Fork是阻塞) |
複雜度 | O(n) | O(n) |
優勢 | 不會消耗額外內存 | 不阻塞客戶端命令 |
缺點 | 阻塞客戶端命令 | 須要Fork子進程,內存開銷大 |
BGSAVE運行流程安全
在redis.config配置文件裏能夠配置自動觸發,配置方式以下:服務器
save <seconds> <changes>
這個配置的規則指的是在seconds秒內發生changes次寫操做,就會自動進行一次bgsave,例如:網絡
save 900 1
指的是若是900秒內有1條Key信息發生變化就會觸發一次bgsave。app
還有在執行shutdown命令的時候,若是沒有開啓AOF持久化功能,那麼會自動執行一次bgsave。異步
和建立文件不一樣,RDB文件的載入是在服務器啓動時自動執行的,並無用於載入RDB文件的命令,只要Redis服務器在啓動時檢測到RDB文件的存在,它就會自動載入RDB文件。能過啓動時日誌記錄能夠查看。須要注意的是,若是打開了AOF持久化,那麼服務器會優先使用AOF文件來還原數據庫狀態。
文件的建立除了SAVE和GBSAVE保存RDB 文件,還能夠經過配置SAVE選項,讓服務器每隔一段時間自動執行一次BGSAVE命令。能夠配置SAVE選項設置多個保存條件,只要任意一個條件被知足,服務器就會執行BGSAVE命令。
--默認配置的SAVE選項,保存方式有三種條件,知足任意一種就能夠,以下:
127.0.0.1:6379> config get save
1) "save" 2) "900 1 300 10 60 10000"
-- 服務器在900秒以內,對數據庫進行了至少1次修改,則發起保存快照。
-- 服務器在300秒以內,對數據庫進行了至少10次修改,則發起保存快照。
-- 服務器在60秒以內,對數據庫進行了至少10000次修改,則發起保存快照。
Redis的服務器週期性操做默認每隔100毫秒就會檢查執行一次,用於對正在運行的服務器進行維護,其中一項工做是檢查save 選項所設置的保存條件是否已經知足,若是知足就調用BGSAVE命令。
- Redis使用fork函數複製一份當前進程(父進程)的副本(子進程);
- 父進程繼續接收並處理客戶端發來的命令,而子進程開始將內存中的數據寫入硬盤中的臨時文件;
- 當子進程寫入完全部數據後會用該臨時文件替換舊的RDB文件,至此一次快照操做完成。
- 在執行fork的時候操做系統(類Unix操做系統)會使用寫時複製(copy-on-write)策略,即fork函數發生的一刻父子進程共享同一內存數據,當父進程要更改其中某片數據時(如執行一個寫命令 ),操做系統會將該片數據複製一份以保證子進程的數據不受影響,因此新的RDB文件存儲的是執行fork一刻的內存數據。
1.只有一個文件dump.rdb,方便持久化。
2.容災性好,一個文件能夠保存到安全的磁盤。
3.性能最大化,fork子進程來完成寫操做,讓主進程繼續處理命令,因此是IO最大化。
4.相對於數據集大時,比AOF的啓動效率更高。
1.數據安全性低,經過配置save參數來達到定時的寫快照,好比 每900 秒有1個鍵被修改就進行一次快照,每600秒至少有10個鍵被修改進行快照,每30秒有至少10000個鍵被修改進行記錄。因此若是當服務器還在等待寫快照時出現了宕機,那麼將會丟失數據。
2.fork子進程時可能致使服務器停機1秒,數據集太大。
AOF(append only file),以日誌的方式記錄每次寫命令,服務重啓的時候從新執行AOF文件中的命令來恢復內存數據。由於解決了數據持久化實時性的問題,因此目前AOF是Redis持久化的主流方式。
當AOF執行處於打開狀態時,服務器在執行完一個寫命令以後,會以協議格式將被執行的寫命令追加到服務器狀態的AOF_buf緩衝區的末尾。
struct redisServer {
sds aof_buf; /* AOF buffer, written before entering the event loop */
}
edis的服務器進程就是一個事件循環(loop),這個循環中的文件事件負責接收客戶端的命令請求,以及向客戶端發送命令回覆。在服務器每次結束一個事件循環以前,都會調用內部flushAppendOnlyFile函數,考慮是否須要將aof_buf緩衝區中的內容寫入和保存到AOF文件裏面。flushAppendOnlyFile函數的行爲由服務器配置appendfsync選項的值來決定。appendfsync是指數據同步到磁盤文件(AOF)的方式,默認配置是everysec選項。當同步頻率是everysec值時,而且距離上次同步AOF文件已經超過一秒時,那麼服務器會先將aof_buf中的內容寫入到AOF文件中。
127.0.0.1:6379>config get Appendfsync
1) "appendfsync"
2) "everysec"
RDB方式是週期性的持久化數據, 若是未到持久化時間點,Redis 由於某些緣由而形成故障停機, 那麼服務器將丟失最近寫入、且仍未保存到快照中的那些數據。因此從redis 1.1開始引入了AOF方式,AOF 持久化記錄服務器執行的全部寫操做命令,並在服務器啓動時,經過從新執行這些命令來還原數據集。 AOF 文件中的命令所有以 Redis 協議的格式來保存,新命令會被追加到文件的末尾。
AOF方式仍然有丟失數據的可能,由於收到寫命令後可能並不會立刻將寫命令寫入磁盤,所以咱們能夠修改redis.conf,配置redis調用write函數寫入命令到文件中的時機。以下
#啓用AOF方式
appendonly yes
#每次有新命令追加到 AOF 文件時就執行一次 fsync :很是慢,也很是安全
#每秒 fsync 一次:足夠快(和使用 RDB 持久化差很少),而且在故障時只會丟失 1 秒鐘的數據
appendfsync everysec
#從不 fsync :將數據交給操做系統來處理。更快,也更不安全的選擇
appendfsync no
Appendfsync模式 |
對應flushAppendOnlyFile函數行爲 |
no |
當設置appendfsync爲no的時候,Redis不會主動調用fsync去將AOF日誌內容同步到磁盤,因此這一切就徹底依賴於操做系統的調試了。對大多數Linux操做系統,是每30秒進行一次fsync,將緩衝區中的數據寫到磁盤上。從效率上講該模式最快, 但同步到磁盤不及時,是最不安全的選擇。 |
Everysec (推薦) |
當設置appendfsync爲everysec的時候,Redis會默認每隔一秒進行一次fsync調用,將緩衝區中的數據寫到磁盤,從效率上講該模式足夠快(和使用 RDB 持久化差很少),而且當出現故障停機時,數據庫也只丟失一秒鐘的命令。 |
always |
當設置appendfsync爲always時,每一次寫操做都會調用一次fsync,這時數據是最安全的,固然,因爲每次都會執行fsync,因此其性能也會受到影響,效率上講該模式最慢的。 |
服務器讀入並從新執行一遍aof文件裏面保存的寫命令,就能夠還原服務器關閉以前的數據庫狀態。
服務器讀取aof文件並還原數據庫狀態的流程:
a.建立一個僞客戶端(由於Redis的命令只能在客戶端上下文中執行),用於載入AOF文件時所使用的命令直接來源於AOF文件,而不是來自網絡鏈接的命令。
b. 從AOF文件中分析並讀出一條寫命令。
c. 使用僞客戶端執行被讀出的寫命令。
d. 重複執行步驟2和3,直到AOF文件中的全部命令都被處理完爲止。
好比:服務器首先讀入並執行select 0 命令,以後是set msg hello命令,再以後是sadd fruits apple banana cherry命令等等,這些命令都執行完以後,服務器的數據庫就被還原到以前的狀態了。
AOF持久化是經過保存被執行的寫命令來記錄數據庫狀態的,隨着時間流逝,Redis服務器執行的寫命令愈來愈多,AOF文件也會愈來愈大;過大的AOF文件不只會影響服務器的正常運行,也會致使數據恢復須要的時間過長。爲了解決AOF文件體積膨脹的問題,Redis提供了AOF文件重寫功能。經過該功能,Redis服務器能夠建立一個新的AOF文件來替代現有的AOF文件,新舊兩個AOF文件所保存的數據庫狀態相同,但新的AOF文件不會包含任何浪費空間的冗餘命令,因此新AOF文件體積比舊的AOF文件體積要小得多。
文件重寫是指按期重寫AOF文件,減少AOF文件的體積。須要注意的是,AOF重寫是把Redis進程內的數據轉化爲寫命令,同步到新的AOF文件;不會對舊的AOF文件進行任何讀取、寫入操做!這個重寫功能是經過讀取服務器當前的數據庫狀態來實現的。好比:使用寫命令 rpush list "A", rpush list "B", rpush list "C", rpush list "D" 此時必須在AOF文件中寫入四條命令。重寫能夠是直接從數據庫中讀取鍵list的值,而後用一條rpush list "A","B","C","D"命令來代替。
文件重寫之因此可以壓縮AOF文件,緣由在於:
a.過時的數據再也不寫入文件
b.無效的命令再也不寫入文件:若有些數據被重複設值(set mykey v1, set mykey v2)、有些數據被刪除了(sadd myset v1, del myset)等等
c.多條命令能夠合併爲一個:如sadd myset v1, sadd myset v2, sadd myset v3能夠合併爲sadd myset v1 v2 v3。不過爲了防止單條命令過大形成客戶端緩衝區溢出,對於list、set、hash、zset類型的key,並不必定只使用一條命令;而是以某個常量爲界將命令拆分爲多條。這個常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定義,不可更改,3.0版本中值是64。
文件重寫的觸發,分爲手動觸發和自動觸發:
手動觸發:直接調用bgrewriteaof命令,該命令的執行與bgsave有些相似:都是fork子進程進行具體的工做,且都只有在fork時阻塞。
自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數,以及aof_current_size和aof_base_size狀態肯定觸發時機。
auto-aof-rewrite-min-size:執行AOF重寫時,文件的最小體積,默認值爲64MB。
auto-aof-rewrite-percentage:執行AOF重寫時,當前AOF大小(即aof_current_size)和上一次重寫時AOF大小(aof_base_size)的比值。
其中,參數能夠經過config get命令查看:
狀態能夠經過info persistence查看:
只有當auto-aof-rewrite-min-size和auto-aof-rewrite-percentage兩個參數同時知足時,纔會自動觸發AOF重寫,即bgrewriteaof操做。
1) Redis父進程首先判斷當前是否存在正在執行 bgsave/bgrewriteaof的子進程,若是存在則bgrewriteaof命令直接返回,若是存在bgsave命令則等bgsave執行完成後再執行。前面曾介紹過,這個主要是基於性能方面的考慮。
2) 父進程執行fork操做建立子進程,這個過程當中父進程是阻塞的。
3.1) 父進程fork後,bgrewriteaof命令返回」Background append only file rewrite started」信息並再也不阻塞父進程,並能夠響應其餘命令。Redis的全部寫命令依然寫入AOF緩衝區,並根據appendfsync策略同步到硬盤,保證原有AOF機制的正確。
3.2) 因爲fork操做使用寫時複製技術,子進程只能共享fork操做時的內存數據。因爲父進程依然在響應命令,所以Redis使用AOF重寫緩衝區(圖中的aof_rewrite_buf)保存這部分數據,防止新AOF文件生成期間丟失這部分數據。也就是說,bgrewriteaof執行期間,Redis的寫命令同時追加到aof_buf和aof_rewirte_buf兩個緩衝區。
4) 子進程根據內存快照,按照命令合併規則寫入到新的AOF文件。
5.1) 子進程寫完新的AOF文件後,向父進程發信號,父進程更新統計信息,具體能夠經過info persistence查看。
5.2) 父進程把AOF重寫緩衝區的數據寫入到新的AOF文件,這樣就保證了新AOF文件所保存的數據庫狀態和服務器當前狀態一致。
5.3) 使用新的AOF文件替換老文件,完成AOF重寫。
重寫做爲一種輔助維護,Redis不但願AOF重寫形成服務器沒法處理請求,因此Redis決定將AOF重寫程序放到子進程裏執行。對AOF 文件進行重寫,執行bgrewriteaof命令, Redis將生成一個新的 AOF 文件,這個文件包含重寫當前數據集所需的最少命令。bgrewriteaof後臺重寫實現步驟以下:
a.Redis執行 fork() ,如今同時擁有父進程和子進程。
b.子進程開始將新 AOF 文件的內容寫入到臨時文件。
c.對於全部新執行的寫入命令,父進程一邊將它們累積到一個內存緩存中,一邊將這些改動追加到現有 AOF 文件的末尾,這樣樣即便在重寫的中途發生停機,現有的 AOF 文件也仍是安全的。
d.當子進程完成重寫工做時,它給父進程發送一個信號,父進程在接收到信號以後,將內存緩存中的全部數據追加到新 AOF 文件的末尾。
e.如今Redis原子地用新文件替換舊文件,以後全部命令都會直接追加到新 AOF 文件的末尾。
appendonly yes //啓用aof持久化方式
# appendfsync always //每收到寫命令就當即強制寫入磁盤,最慢的,可是保證徹底的持久化,不推薦使用
appendfsync everysec //每秒強制寫入磁盤一次,性能和持久化方面作了折中,推薦
# appendfsync no //徹底依賴os,性能最好,持久化沒保證(操做系統自身的同步)
no-appendfsync-on-rewrite yes //正在導出rdb快照的過程當中,要不要中止同步aof
auto-aof-rewrite-percentage 100 //aof文件大小比起上次重寫時的大小,增加率100%時,重寫
auto-aof-rewrite-min-size 64mb //aof文件,至少超過64M時,重寫
具體配置相關項參照如下表格
選項 |
取值 |
說明 |
Appendonly |
no|yes |
是否開啓AOF機制 |
Appendfilename |
"appendonly.aof" |
Aof文件名 |
Appendfsync |
no|appendfsync|always |
AOF持久化同步頻率 |
no-appendfsync-on-rewrite |
No | yes |
在日誌進行BGREWRITEAOF時,若是設置爲yes表示新寫操做不進行同步fsync,只是暫存在緩衝區裏,避免形成磁盤IO操做衝突,等重寫完成後在寫入。redis中默認爲no
|
auto-aof-rewrite-percentage |
100 |
當前AOF文件大小是上第二天志重寫時的AOF文件大小兩倍時,發生BGREWRITEAOF操做。 |
auto-aof-rewrite-min-size |
64mb |
當前AOF文件執行BGREWRITEAOF命令的最小值,避免剛開始啓動Reids時因爲文件尺寸較小致使頻繁的BGREWRITEAOF。 |
aof-load-truncated |
yes |
Redis再恢復時,忽略最後一條可能存在問題的指令 |
aof-use-rdb-preamble |
no |
新增RDB-AOF混合持久化格式,在開啓了這個功能以後,AOF重寫產生的文件將同時包含RDB格式的內容和AOF格式的內容 |
1). 該機制能夠帶來更高的數據安全性,即數據持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不一樣步。事實上,每秒同步也是異步完成的,其效率也是很是高的,所差的是一旦系統出現宕機現象,那麼這一秒鐘以內修改的數據將會丟失。而每修改同步,咱們能夠將其視爲同步持久化,即每次發生的數據變化都會被當即記錄到磁盤中。能夠預見,這種方式在效率上是最低的。至於無同步,無需多言,我想你們都能正確的理解它。
2). 因爲該機制對日誌文件的寫入操做採用的是append模式,所以在寫入過程當中即便出現宕機現象,也不會破壞日誌文件中已經存在的內容。然而若是咱們本次操做只是寫入了一半數據就出現了系統崩潰問題,不用擔憂,在Redis下一次啓動以前,咱們能夠經過redis-check-aof工具來幫助咱們解決數據一致性的問題。
3). 若是日誌過大,Redis能夠自動啓用rewrite機制。即Redis以append模式不斷的將修改數據寫入到老的磁盤文件中,同時Redis還會建立一個新的文件用於記錄此期間有哪些修改命令被執行。所以在進行rewrite切換時能夠更好的保證數據安全性。
4). AOF包含一個格式清晰、易於理解的日誌文件用於記錄全部的修改操做。事實上,咱們也能夠經過該文件完成數據的重建。
1). 對於相同數量的數據集而言,AOF文件一般要大於RDB文件。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
2). 根據同步策略的不一樣,AOF在運行效率上每每會慢於RDB。總之,每秒同步策略的效率是比較高的,同步禁用策略的效率和RDB同樣高效。
RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤,實際操做過程是fork一個子進程,先將數據集寫入臨時文件,寫入成功後,再替換以前的文件,用二進制壓縮存儲。
AOF持久化以日誌的形式記錄服務器所處理的每個寫、刪除操做,查詢操做不會記錄,以文本的方式記錄,能夠打開文件看到詳細的操做記錄。
RDB | AOF | |
存儲數據 | 保存鍵空間的全部鍵值對(包括過時字典中的數據),並以二進制形式保存,符合rdb文件規範,根據不一樣數據類型會有不一樣處理 |
保存redis服務器所執行的全部寫命令來記錄數據庫狀態,在寫入以前命令存儲在aof_buf緩衝區。 |
持久化時間選擇 | 經過conf的save選項設置持久化行爲(單位時間內的修改次數)。 |
經過conf的appendfsync選項設置持久化行爲 |
數據還原 | 服務器載入rdb文件,阻塞線程,在載入完成以前不接受任何命令。 |
服務器建立不帶網絡鏈接的僞客戶端,讀取aof文件中的全部命令並執行(redis服務開啓aof持久化在服務器啓動時會選擇aof文件恢復數據庫狀態) |
過時鍵 | 在寫入或讀取時會忽略過時鍵 | 不會忽略過時鍵,在鍵被惰性刪除或按期刪除時向aof文件追加一條刪除命令 |
文件大小 | 隨着存儲數據量的變化而變化(根據不一樣數據類型有不一樣的數據壓縮優化) | 隨着執行命令的增長而增長(經過aof重寫進行優化) |
執行命令 | SAVE和BGSAVE | -- |
在Redis中,不管是RDB持久化的bgsave,仍是AOF重寫的bgrewriteaof,都須要fork出子進程來進行操做。若是Redis內存過大,會致使fork操做時複製內存頁表耗時過多;而Redis主進程在進行fork時,是徹底阻塞的,也就意味着沒法響應客戶端的請求,會形成請求延遲過大。
對於不一樣的硬件、不一樣的操做系統,fork操做的耗時會有所差異,通常來講,若是Redis單機內存達到了10GB,fork時耗時可能會達到百毫秒級別(若是使用Xen虛擬機,這個耗時可能達到秒級別)。所以,通常來講Redis單機內存通常要限制在10GB之內;不過這個數據並非絕對的,能夠經過觀察線上環境fork的耗時來進行調整。觀察的方法以下:執行命令info stats,查看latest_fork_usec的值,單位爲微秒。
爲了減輕fork操做帶來的阻塞問題,除了控制Redis單機內存的大小之外,還能夠適度放寬AOF重寫的觸發條件、選用物理機或高效支持fork操做的虛擬化技術等,例如使用Vmware或KVM虛擬機,不要使用Xen虛擬機。
當開啓aof持久化功能時,文件刷盤的方式通常採用每秒一次,後臺線程每秒對aof文件作fsync操做,硬盤壓力過大時,fsync操做須要等待,直到寫入完成若是主線程發現距離上一次的fsync成功超過2秒,爲了數據安全性它會阻塞直到後臺線程執行fsync操做完成,這種阻塞行爲主要是硬盤壓力引發,可查看info persistence統計中的aof_delayed_fsync指標,每次發生aof刷盤阻塞主線程時會累加。
AOF追加阻塞問題定位的方法:
(1)監控info Persistence中的aof_delayed_fsync:當AOF追加阻塞發生時(即主線程等待fsync而阻塞),該指標累加。
(2)AOF阻塞時的Redis日誌:
Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
(3)若是AOF追加阻塞頻繁發生,說明系統的硬盤負載太大;能夠考慮更換IO速度更快的硬盤,或者經過IO監控分析工具對系統的IO負載進行分析,如iostat(系統級io)、iotop(io版的top)、pidstat等。
本文主要內容能夠總結以下:
一、持久化在Redis高可用中的做用:數據備份,與主從複製相比強調的是由內存到硬盤的備份。
二、RDB持久化:將數據快照備份到硬盤;介紹了其觸發條件(包括手動出發和自動觸發)、執行流程、RDB文件等,特別須要注意的是文件保存操做由fork出的子進程來進行。
三、AOF持久化:將執行的寫命令備份到硬盤(相似於MySQL的binlog),介紹了其開啓方法、執行流程等,特別須要注意的是文件同步策略的選擇(everysec)、文件重寫的流程。
四、一些現實的問題:包括如何選擇持久化策略,以及須要注意的fork阻塞、AOF追加阻塞等。
https://www.cnblogs.com/bamaofan/p/5284014.html
https://www.cnblogs.com/MrHSR/p/9999957.html
https://www.cnblogs.com/hjwublog/p/5660578.html
https://www.cnblogs.com/leeSmall/p/8379768.html
https://www.cnblogs.com/kevingrace/p/5685332.html
https://blog.csdn.net/Leon_cx/article/details/81545178
https://blog.csdn.net/qq_35433716/article/details/82195106
https://blog.csdn.net/hangbo216/article/details/68925644
https://www.cnblogs.com/MrHSR/P/10045533.html
https://www.cnblogs.com/chenliangcl/p/7240350.html
https://www.cnblogs.com/xingzc/p/5988080.html
https://www.cnblogs.com/jieliu726/p/9045942.html
https://blog.csdn.net/fjj15732621696/article/details/81842075
https://blog.csdn.net/linbiaorui/article/details/79822318