衆所周知,Redis是內存數據庫,且使用單個線程來處理命令請求。它將本身的數據庫狀態(非空數據庫以及它們的鍵值對)存儲在內存裏面。因此若是沒有持久化機制,不把數據保存到硬盤裏面,那麼一旦服務器進程退出,服務器中的數據庫狀態也會消失不見。
爲了解決這個問題,redis 提供兩種方法進行數據持久化,分別是RDB和AOF。RDB能夠將Redis在內存中的數據庫狀態保存到磁盤裏面以實現持久化,AOF經過記錄寫命令達到持久化效果。兩種方法都有各自的優勢,須要咱們在生產環境中依照實際的業務狀況進行裁定。服務器按照下面的流程圖選擇持久化方式:
web
RDB持久化既能夠手動執行,也能夠依據配置文件選項按期執行。RDB持久化生成的RDB文件是一個二進制文件,經過此二進制文件可以還原數據庫狀態。
如圖:
RDB提供兩種方式生成RDB文件,分別經過執行SAVE和BGSAVE命令生產RDB文件。SAVE由服務器進程直接執行保存,它會阻塞服務器的進程。BGSAVE是服務器子進程執行保存操做,它的運行不會阻塞服務器進程的運行。redis
上面已經說過,SAVE命令執行時會阻塞服務器的運行。所以,在服務器執行SAVE命令時,客戶端發送的全部命令請求都會被拒絕。只有服務器執行完SAVE命令,服務器纔會從新接收客戶端發送的命令請求。數據庫
與SAVE相反,BGSAVE由服務器子進程執行,在執行時依然能夠接收客戶端發送命令並處理。可是,服務器處理SAVE、BGSAVE、BGREWRITEAOF三種方式和平時有些不同。
數組
對於SAVE,在執行BGSAVE時,客戶端發送的SAVE命令會被服務器拒絕。服務器禁止SAVE和BGSAVE命令同時執行是爲了防止父進程(服務器進程)和子進行同時執行產生競爭條件
安全
對於BGSAVE。在執行BGSAVE時,客戶端發送的BGSAVE命令時一樣也是被拒絕。緣由是爲了防止兩個BGSAVE產生競爭條件。
服務器
對於BGREWRITEAOF。在執行BGSAVE時,也是被拒絕的。緣由是由於BGSAVE和BGREWRITEAOF不能同時執行。
app
在陳述原理以前,先來看下幾個與按期持久化密切相關的屬性。
函數
saveparams屬性
在服務啓動後,服務器會讀取save的值賦給saveparams。save 是redis.conf
中的一個配置文件,它在服務器中的默認配置爲:性能
save 900 1 //服務器在900秒以內,對數據庫進行了至少1次修改。 save 300 10 //服務器在300秒以內,對數據庫進行了至少10次修改。 save 60 10000 //服務器在60秒以內,對數據庫進行了至少10000次修改。
服務器狀態redisServer中saveparams結構以下:
spa
struct redisServer { // ... // 記錄了保存條件的數組 struct saveparam *saveparams; // ... }; struct saveparam { // 秒數 time_t seconds; // 修改數 int changes; };
若是服務器中SAVE屬性爲默認配置,那麼服務器中的狀態將會下面這樣的。
dirty計數器和lastsave屬性
除了saveparams數組以外,服務器狀態還維持着一個dirty計數器,以及一個lastsave屬性:
dirty計數器記錄距離上一次成功執行SAVE命令或者BGSAVE命令以後,服務器對數據庫狀態(服務器中的全部數據庫)進行了多少次修改(包括寫入、刪除、更新等操做)。
lastsave屬性是一個UNIX時間戳,記錄了服務器上一次成功執行SAVE命令或者BGSAVE命令的時間。
dirty和lastsave在服務器狀態中的結構以下:
struct redisServer { // ... // 修改計數器 long long dirty; // 上一次執行保存的時間 time_t lastsave; // ... };
上面已經介紹完幾個重要的屬性了,如今開始切入正題了。
若是未開啓AOF功能,那麼在Redis啓動後,Redis的服務器週期性操做函數serverCron默認每隔100毫秒就會執行一次,它的其中一項工做就是檢查save選項所設置的保存條件是否已經知足。若是知足的話,就執行BGSAVE命令。下面是ServerCron函數的邏輯代碼
def serverCron(): # … # 遍歷全部保存條件 for saveparam in server.saveparams: # 計算距離上次執行保存操做有多少秒 save_interval = unixtime_now()-server.lastsave # 若是數據庫狀態的修改次數超過條件所設置的次數 # 而且距離上次保存的時間超過條件所設置的時間 # 那麼執行保存操做 if server.dirty >= saveparam.changes and \ save_interval > saveparam.seconds: BGSAVE(); # ...
舉個例子,若是Redis服務器的當前狀態以下圖所示
那麼當時間來到1378271101,也便是1378270800的301秒以後,服務器將自動執行一次BGSAVE命令,由於saveparams數組的第二個保存條件——300秒以內有至少10次修改——已經被知足。
假設BGSAVE在執行5秒以後完成,那麼上圖所示的服務器狀態將更新爲下圖所示的狀態,其中dirty計數器已經被重置爲0,而lastsave屬性也被更新爲1378271106。
以上就是Redis服務器根據save選項所設置的保存條件,自動執行BGSAVE命令,進行間隔性數據保存的實現原理。
AOF持久化是經過保存Redis服務器所執行的寫命令來記錄數據庫狀態的。AOF持久化命令能夠分爲命令追加(append)、文件寫入、文件同步三個步驟。開啓AOF持久化的配置 appendonly yes
resist服務器中的結構是這樣的
struct redisServer { // ... // AOF緩衝區 sds aof_buf; // ... }
服務器在執行一個寫命令後,會將命令追加到 aof_buf 緩衝區的末尾。
將命令寫入到aof文件中去
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,就是組合屢次寫操做的數據,一次性將日誌寫到磁盤。
appendfsync always (每一次寫操做都會執行同步操做) 置appendfsync爲always時,每一次寫操做都會調用一次fsync,這時數據是最安全的,固然,因爲每次都會執行fsync,因此其性能也會受到響。