REmote DIctionary Server(Redis)
一種面向“key-value”類型數據的分佈式NoSQL數據庫系統,具有高性能、持久存儲、適應高併發應用場景等優勢。它雖然起步較晚,但發展卻十分迅速。
Redis提供的持久化機制
文章主要包含三個方面:Redis持久化是如何工作的、這一性能是否可靠以及和其它類型的數據庫比較。以下爲文章內容:
一、Redis持久化是如何工作的?
什麼是持久化?簡單來講就是將數據放到斷電後數據不會丟失的設備中,也就是我們通常理解的硬盤上。
首先我們來看一下數據庫在進行寫操作時到底做了哪些事,主要有下面五個過程:
- 客戶端向服務端發送寫操作(數據在客戶端的內存中)。
- 數據庫服務端接收到寫請求的數據(數據在服務端的內存中)。
- 服務端調用write這個系統調用,將數據往磁盤上寫(數據在系統內存的緩衝區中)。
- 操作系統將緩衝區中的數據轉移到磁盤控制器上(數據在磁盤緩存中)。
- 磁盤控制器將數據寫到磁盤的物理介質中(數據真正落到磁盤上)。
故障分析
寫操作大致有上面5個流程,下面我們結合上面的5個流程看一下各種級別的故障:
當數據庫系統故障時,這時候系統內核還是完好的。那麼此時只要我們執行完了第3步,那麼數據就是安全的,因爲後續操作系統會來完成後面幾步,保證數據最終會落到磁盤上。
當系統斷電時,這時候上面5項中提到的所有緩存都會失效,並且數據庫和操作系統都會停止工作。所以只有當數據在完成第5步後,才能保證在斷電後數據不丟失。
通過上面5步的瞭解,可能我們會希望搞清下面一些問題:
- 數據庫多長時間調用一次write,將數據寫到內核緩衝區?
- 內核多長時間會將系統緩衝區中的數據寫到磁盤控制器?
- 磁盤控制器又在什麼時候把緩存中的數據寫到物理介質上?
1. 對於第一個問題,通常數據庫層面會進行全面控制。
2. 而對第二個問題,操作系統有其默認的策略,但是我們也可以通過POSIX API提供的fsync系列命令強制操作系統將數據從內核區寫到磁盤控制器上。
3. 對於第三個問題,好像數據庫已經無法觸及,但實際上,大多數情況下磁盤緩存是被設置關閉的,或者是隻開啓爲讀緩存,也就是說寫操作不會進行緩存,直接寫到磁盤。
4. 建議的做法是僅僅當你的磁盤設備有備用電池時纔開啓寫緩存。
數據損壞
所謂數據損壞,就是數據無法恢復,上面我們講的都是如何保證數據是確實寫到磁盤上去,但是寫到磁盤上可能並不意味着數據不會損壞。比如我們可能一次寫請求會進行兩次不同的寫操作,當意外發生時,可能會導致一次寫操作安全完成,但是另一次還沒有進行。如果數據庫的數據文件結構組織不合理,可能就會導致數據完全不能恢復的狀況出現。
這裏通常也有三種策略來組織數據,以防止數據文件損壞到無法恢復的情況:
- 第一種是最粗糙的處理,就是不通過數據的組織形式保證數據的可恢復性。而是通過配置數據同步備份的方式,在數據文件損壞後通過數據備份來進行恢復。實際上MongoDB在不開啓操作日誌,通過配置Replica Sets時就是這種情況。
- 另一種是在上面基礎上添加一個操作日誌,每次操作時記一下操作的行爲,這樣我們可以通過操作日誌來進行數據恢復。因爲操作日誌是順序追加的方式寫的,所以不會出現操作日誌也無法恢復的情況。這也類似於MongoDB開啓了操作日誌的情況。
- 更保險的做法是數據庫不進行舊數據的修改,只是以追加方式去完成寫操作,這樣數據本身就是一份日誌,這樣就永遠不會出現數據無法恢復的情況了。實際上CouchDB就是此做法的優秀範例。
二 、Redis提供了RDB持久化和AOF持久化
RDB機制的優勢和略施
RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。
也是默認的持久化方式,這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲dump.rdb。
可以通過配置設置自動做快照持久化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置
save 900 1 #900秒內如果超過1個key被修改,則發起快照保存
save 300 10 #300秒內容如超過10個key被修改,則發起快照保存
save 60 10000 #60秒內容如超過10000個key被修改,則發起快照保存
RDB文件保存過程
- redis調用fork,現在有了子進程和父進程。
- 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由於os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數 據是fork時刻整個數據庫的一個快照。
- 當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,然後子進程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線程中保存快照的,由於redis是用一個主線程來處理所有 client的請求,這種方式會阻塞所有client請求。所以不推薦使用。 - 另一點需要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,並不 是增量的只同步髒數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。
優勢
- 一旦採用該方式,那麼你的整個Redis數據庫將只包含一個文件,這樣非常方便進行備份。比如你可能打算沒1天歸檔一些數據。
- 方便備份,我們可以很容易的將一個一個RDB文件移動到其他的存儲介質上
- RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
- RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然後這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。
劣勢
- 如果你需要儘量避免在服務器故障時丟失數據,那麼 RDB 不適合你。 雖然 Redis 允許你設置不同的保存點(save point)來控制保存 RDB 文件的頻率, 但是, 因爲RDB 文件需要保存整個數據集的狀態, 所以它並不是一個輕鬆的操作。 因此你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種情況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。
- . 每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工作。 在數據集比較龐大時, fork() 可能會非常耗時,造成服務器在某某毫秒內停止處理客戶端; 如果數據集非常巨大,並且 CPU 時間非常緊張的話,那麼這種停止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。
AOF文件保存過程
-
redis會將每一個收到的寫命令都通過write函數追加到文件中(默認是 appendonly.aof)。
-
當redis重啓時會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。當然由於os會在內核中緩存 write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會丟失部分修改。不過我們可以通過配置文件告訴redis我們想要 通過fsync函數強制os寫入到磁盤的時機。有三種方式如下(默認是:每秒fsync一次)
-
appendonly yes //啓用aof持久化方式
-
appendfsync always //每次收到寫命令就立即強制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
-
appendfsync everysec //每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
appendfsync no //完全依賴os,性能最好,持久化沒保證
-
aof 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。例如我們調用incr test命令100次,文件中必須保存全部的100條命令,其實有99條都是多餘的。因爲要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。
-
爲了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內存中的數據 以命令的方式保存到臨時文件中,最後替換原來的文件。
-
具體過程如下
- redis調用fork ,現在有父子兩個進程
- 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令
- 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證如果子進程重寫失敗的話並不會出問題。
- 當子進程把快照內容寫入已命令方式寫到臨時文件中後,子進程發信號通知父進程。然後父進程把緩存的寫命令也寫入到臨時文件。
- 現在父進程可以使用臨時文件替換老的aof文件,並重命名,後面收到的寫命令也開始往新的aof文件中追加。
- 需要注意到是重寫aof文件的操作,並沒有讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。
優勢
-
使用 AOF 持久化會讓 Redis 變得非常耐久(much more durable):你可以設置不同的 fsync 策略,比如無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的性能,並且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,所以主線程可以繼續努力地處理命令請求)。
-
AOF 文件是一個只進行追加操作的日誌文件(append only log), 因此對 AOF 文件的寫入不需要進行 seek , 即使日誌因爲某些原因而包含了未寫入完整的命令(比如寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也可以輕易地修復這種問題。
-
Redis 可以在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因爲 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。
-
AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。
劣勢
-
對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。
-
根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。
-
AOF 在過去曾經發生過這樣的 bug : 因爲個別命令的原因,導致 AOF 文件在重新載入時,無法將數據集恢復成保存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引起過這樣的 bug 。) 測試套件裏爲這種情況添加了測試: 它們會自動生成隨機的、複雜的數據集, 並通過重新載入這些數據來確保一切正常。 雖然這種 bug 在 AOF 文件中並不常見, 但是對比來說, RDB 幾乎是不可能出現這種 bug 的。
抉擇
一般來說, 如果想達到足以媲美 PostgreSQL 的數據安全性, 你應該同時使用兩種持久化功能。
如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失, 那麼你可以只使用 RDB 持久化。
其餘情況我個人喜好選擇AOF
三、其他
如何修復壞損的AOF文件:
1). 將現有已經壞損的AOF文件額外拷貝出來一份。
2). 執行"redis-check-aof --fix "命令來修復壞損的AOF文件。
3). 用修復後的AOF文件重新啓動Redis服務器。
Redis的數據備份:
在Redis中我們可以通過copy的方式在線備份正在運行的Redis數據文件。這是因爲RDB文件一旦被生成之後就不會再被修改。Redis每次都是將最新的數據dump到一個臨時文件中,之後在利用rename函數原子性的將臨時文件改名爲原有的數據文件名。因此我們可以說,在任意時刻copy數據文件都是安全的和一致的。鑑於此,我們就可以通過創建cron job的方式定時備份Redis的數據文件,並將備份文件copy到安全的磁盤介質中。
Redis持久化性能是否可靠?
從上面的流程我們能夠看到,RDB是順序IO操作,性能很高。而同時在通過RDB文件進行數據庫恢復的時候,也是順序的讀取數據加載到內存中。所以也不會造成磁盤的隨機讀取錯誤。
而AOF是一個寫文件操作,其目的是將操作日誌寫到磁盤上,所以它也同樣會遇到我們上面說的寫操作的5個流程。那麼寫AOF的操作安全性又有多高呢?實際上這是可以設置的,在Redis中對AOF調用write寫入後,何時再調用fsync將其寫到磁盤上,通過appendfsync選項來控制,下面appendfsync的三個設置項,安全強度逐漸變強。
-
appendfsync no
當設置appendfsync爲no的時候,Redis不會主動調用fsync去將AOF日誌內容同步到磁盤,所以這一切就完全依賴於操作系統的調試了。對大多數Linux操作系統,是每30秒進行一次fsync,將緩衝區中的數據寫到磁盤上。 -
appendfsync everysec
當設置appendfsync爲everysec的時候,Redis會默認每隔一秒進行一次fsync調用,將緩衝區中的數據寫到磁盤。但是當這一 次的fsync調用時長超過1秒時。Redis會採取延遲fsync的策略,再等一秒鐘。也就是在兩秒後再進行fsync,這一次的fsync就不管會執行多長時間都會進行。這時候由於在fsync時文件描述符會被阻塞,所以當前的寫操作就會阻塞。
所以,結論就是:在絕大多數情況下,Redis會每隔一秒進行一次fsync。在最壞的情況下,兩秒鐘會進行一次fsync操作。
這一操作在大多數數據庫系統中被稱爲group commit,就是組合多次寫操作的數據,一次性將日誌寫到磁盤。 -
appednfsync always
當設置appendfsync爲always時,每一次寫操作都會調用一次fsync,這時數據是最安全的,當然,由於每次都會執行fsync,所以其性能也會受到影響。
對於pipelining有什麼不同?
對於pipelining的操作,其具體過程是客戶端一次性發送N個命令,然後等待這N個命令的返回結果被一起返回。通過採用pipilining 就意味着放棄了對每一個命令的返回值確認。由於在這種情況下,N個命令是在同一個執行過程中執行的。所以當設置appendfsync爲everysec 時,可能會有一些偏差,因爲這N個命令可能執行時間超過1秒甚至2秒。但是可以保證的是,最長時間不會超過這N個命令的執行時間和。
和其它數據庫的比較
上面操作系統層面的數據安全我們已經講了很多,其實,不同的數據庫在實現上都大同小異。總之,最後的結論就是,在Redis開啓AOF的情況下,其單機數據安全性並不比這些成熟的SQL數據庫弱。
在數據導入方面的比較
這些持久化的數據有什麼用,當然是用於重啓後的數據恢復。Redis是一個內存數據庫,無論是RDB還是AOF,都只是其保證數據恢復的措施。所以 Redis在利用RDB和AOF進行恢復的時候,都會讀取RDB或AOF文件,重新加載到內存中。相對於MySQL等數據庫的啓動時間來說,會長很多,因爲MySQL本來是不需要將數據加載到內存中的。
但是相對來說,MySQL啓動後提供服務時,其被訪問的熱數據也會慢慢加載到內存中,通常我們稱之爲預熱,而在預熱完成前,其性能都不會太高。而Redis的好處是一次性將數據加載到內存中,一次性預熱。這樣只要Redis啓動完成,那麼其提供服務的速度都是非常快的。
而在利用RDB和利用AOF啓動上,其啓動時間有一些差別。RDB的啓動時間會更短,原因有兩個,一是RDB文件中每一條數據只有一條記錄,不會像 AOF日誌那樣可能有一條數據的多次操作記錄。所以每條數據只需要寫一次就行了。另一個原因是RDB文件的存儲格式和Redis數據在內存中的編碼格式是一致的,不需要再進行數據編碼工作。在CPU消耗上要遠小於AOF日誌的加載。