【Redis—基礎】持久化

RDB

        RDB持久化是Redis默認使用的持久化功能,該功能能夠建立出一個通過壓縮的二進制文件,其中包含了服務器在各個數據庫中存儲的鍵值對數據等信息。RDB持久化產生的文件都以.rdb後綴結尾,其中rdb表明Redis DataBase(Redis數據庫)。ios

1.觸發機制

        手動觸發分別對應save和bgsave命令:redis

        ·save命令:阻塞當前Redis服務器,直到RDB過程完成爲止,對於內存比較大的實例會形成長時間阻塞,線上環境不建議使用。算法

        ·bgsave命令:Redis進程執行fork操做建立子進程,RDB持久化過程由子進程負責,完成後自動結束。阻塞只發生在fork階段,通常時間很短。數據庫

        除了執行命令手動觸發以外,Redis內部還存在自動觸發RDB的持久化機制,例如如下場景:安全

        1)使用save相關配置,如「save m n」。表示m秒內數據集存在n次修改時,自動觸發bgsave服務器

        2)若是從節點執行全量複製操做,主節點自動執行bgsave生成RDB文件併發送給從節點網絡

        3)執行debug reload命令從新加載Redis時,也會自動觸發save操做。架構

        4)默認狀況下執行shutdown命令時,若是沒有開啓AOF持久化功能則自動執行bgsave。併發

2.流程

        1)執行bgsave命令,Redis父進程判斷當前是否存在正在執行的子進程,如RDB/AOF子進程,若是存在bgsave命令直接返回。app

        2)父進程執行fork操做建立子進程,fork操做過程當中父進程會阻塞經過info stats命令查看latest_fork_usec選項,能夠獲取最近一個fork操做的耗時,單位爲微秒

        3)父進程fork完成後,bgsave命令返回「Background saving started」信息並再也不阻塞父進程,能夠繼續響應其餘命令。

        4)子進程建立RDB文件,根據父進程內存生成臨時快照文件,完成後對原有文件進行原子替換。執行lastsave命令能夠獲取最後一次生成RDB的時間,對應info統計的rdb_last_save_time選項

        5)進程發送信號給父進程表示完成,父進程更新統計信息,具體見info Persistence下的rdb_*相關選項。

3.RDB文件的處理

        保存RDB文件保存在dir配置指定的目錄下,文件名經過dbfilename配置指定。能夠經過執行config set dir {newDir}和config set dbfilename {newFileName}運行期動態執行,當下次運行時RDB文件會保存到新目錄。

        當遇到壞盤或磁盤寫滿等狀況時,能夠經過config set dir {newDir}在線修改文件路徑到可用的磁盤路徑,以後執行bgsave進行磁盤切換,一樣適用於AOF持久化文件。

        壓縮:Redis默認採用LZF算法對生成的RDB文件作壓縮處理,壓縮後的文件遠遠小於內存大小,默認開啓,能夠經過參數config set rdbcompression {yes|no}動態修改

        雖然壓縮RDB會消耗CPU,但可大幅下降文件的體積,方便保存到硬盤或經過網絡發送給從節點,所以線上建議開啓。

        校驗:若是Redis加載損壞的RDB文件時拒絕啓動,並打印錯誤日誌。這時可使用Redis提供的redis-check-dump工具檢測RDB文件並獲取對應的錯誤報告

4.RDB的優缺點

        RDB的優勢:

        1)RDB是一個緊湊壓縮的二進制文件,表明Redis在某個時間點上的數據快照。很是適用於冷備份,全量複製等場景。好比每6小時執行bgsave備份,並把RDB文件拷貝到遠程機器或者文件系統中(如hdfs),用於災難恢復。

        2)Redis加載RDB恢復數據遠遠快於AOF的方式。

        RDB的缺點:

        1)RDB方式數據沒辦法作到實時持久化/秒級持久化。由於bgsave每次運行都要執行fork操做建立子進程,屬於重量級操做,頻繁執行成本太高。

        2)RDB文件使用特定二進制格式保存,Redis版本演進過程當中有多個格式的RDB版本,存在老版本Redis服務沒法兼容新版RDB格式的問題。

AOF

        AOF(append only file)持久化:以獨立日誌的方式記錄每次寫命令,重啓時再從新執行AOF文件中的命令達到恢復數據的目的。AOF的主要做用是解決了數據持久化的實時性,目前已是Redis持久化的主流方式。

1.使用AOF

        開啓AOF功能須要設置配置:appendonly yes,默認不開啓AOF文件名經過appendfilename配置設置,默認文件名是appendonly.aof。保存路徑同RDB持久化方式一致,經過dir配置指定。AOF的工做流程操做:命令寫入(append)、文件同步(sync)、文件重寫(rewrite)、重啓加載(load),如圖所示。

        流程以下:

        1)全部的寫入命令會追加到aof_buf(緩衝區)中。

        2)AOF緩衝區根據對應的策略向硬盤作同步操做。

        3)隨着AOF文件愈來愈大,須要按期對AOF文件進行重寫,達到壓縮的目的。

        4)當Redis服務器重啓時,能夠加載AOF文件進行數據恢復。

2.命令寫入

        AOF命令寫入的內容直接是文本協議格式。例如set hello world這條命令,在AOF緩衝區會追加以下文本:*3\r\n$3\\r\\nset\\r\\n$5\r\nhello\r\n$5\r\nworld\r\n,下面介紹關於AOF的兩個疑惑:

        1)AOF爲何直接採用文本協議格式?可能的理由以下:

        · 文本協議具備很好的兼容性。

        · 開啓AOF後,全部寫入命令都包含追加操做,直接採用協議格式,避免了二次處理開銷。

        · 文本協議具備可讀性,方便直接修改和處理。

        2)AOF爲何把命令追加到aof_buf中?Redis使用單線程響應命令,若是每次寫AOF文件命令都直接追加到硬盤,那麼性能徹底取決於當前硬盤負載。先寫入緩衝區aof_buf中,還有另外一個好處,Redis能夠提供多種緩衝區同步硬盤的策略,在性能和安全性方面作出平衡。

3.文件同步

        Redis提供了多種AOF緩衝區同步文件策略,由參數appendfsync控制,不一樣值的含義如表所示。

        write操做會觸發延遲寫(delayed write)機制。Linux在內核提供頁緩衝區用來提升硬盤IO性能。write操做在寫入系統緩衝區後直接返回。同步硬盤操做依賴於系統調度機制,例如:緩衝區頁空間寫滿或達到特定時間週期。同步文件以前,若是此時系統故障宕機,緩衝區內數據將丟失。fsync針對單個文件操做(好比AOF文件),作強制硬盤同步,fsync將阻塞直到寫入硬盤完成後返回,保證了數據持久化。

        · 配置爲always時,每次寫入都要同步AOF文件,在通常的SATA硬盤上,Redis只能支持大約幾百TPS寫入,顯然跟Redis高性能特性背道而馳,不建議配置。

        · 配置爲no,因爲操做系統每次同步AOF文件的週期不可控,並且會加大每次同步硬盤的數據量,雖然提高了性能,但數據安全性沒法保證。

        · 配置爲everysec,是建議的同步策略,也是默認配置,作到兼顧性能和數據安全性。理論上只有在系統忽然宕機的狀況下丟失1秒的數據。(嚴格來講最多丟失1秒數據是不許確的)

4.重寫機制

        隨着命令不斷寫入AOF,文件會愈來愈大,爲了解決這個問題,Redis引入AOF重寫機制壓縮文件體積。AOF文件重寫是把Redis進程內的數據轉化爲寫命令同步到新AOF文件的過程。

        重寫後的AOF文件爲何能夠變小?有以下緣由:

        1)進程內已經超時的數據再也不寫入文件。

        2)舊的AOF文件含有無效命令,重寫使用進程內數據直接生成,這樣新的AOF文件只保留最終數據的寫入命令。

        3)多條寫命令能夠合併爲一個,爲了防止單條命令過大形成客戶端緩衝區溢出,對於list、set、hash、zset等類型操做,以64個元素爲界拆分爲多條。

        AOF重寫下降了文件佔用空間,除此以外,另外一個目的是:更小的AOF文件能夠更快地被Redis加載。

        AOF重寫過程能夠手動觸發和自動觸發:

        · 手動觸發:直接調用bgrewriteaof命令。

        · 自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數肯定自動觸發時機。auto-aof-rewrite-min-size:表示運行AOF重寫時文件最小體積,默認爲64MB。auto-aof-rewrite-percentage:表明當前AOF文件空間(aof_current_size)和上一次重寫後AOF文件空間(aof_base_size)的比值。

        自動觸發時機=aof_current_size>auto-aof-rewrite-min-size&&(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewrite-percentage

        其中aof_current_size和aof_base_size能夠在info Persistence統計信息中查看

        1)執行AOF重寫請求。

        若是當前進程正在執行AOF重寫,請求不執行。若是當前進程正在執行bgsave操做,重寫命令延遲到bgsave完成以後再執行。

        2)父進程執行fork建立子進程,開銷等同於bgsave過程。

        3.1)主進程fork操做完成後,繼續響應其餘命令。全部修改命令依然寫入AOF緩衝區並根據appendfsync策略同步到硬盤,保證原有AOF機制正確性。

        3.2)因爲fork操做運用寫時複製技術,子進程只能共享fork操做時的內存數據。因爲父進程依然響應命令,Redis使用「AOF重寫緩衝區」保存這部分新數據,防止新AOF文件生成期間丟失這部分數據

        4)子進程根據內存快照,按照命令合併規則寫入到新的AOF文件。每次批量寫入硬盤數據量由配置aof-rewrite-incremental-fsync控制,默認爲32MB,防止單次刷盤數據過多形成硬盤阻塞

        5.1)新AOF文件寫入完成後,子進程發送信號給父進程,父進程更新統計信息,具體見info persistence下的aof_*相關統計。

        5.2)父進程把AOF重寫緩衝區的數據寫入到新的AOF文件。

        5.3)使用新AOF文件替換舊文件,完成AOF重寫。

5.重啓加載

6.文件校驗

        加載損壞的AOF文件時會拒絕啓動,對於錯誤格式的AOF文件,先進行備份,而後採用redis-check-aof--fix命令進行修復,修復後使用diff-u對比數據的差別,找出丟失的數據,有些能夠人工修改補全。

        AOF文件可能存在結尾不完整的狀況,好比機器忽然掉電致使AOF尾部文件命令寫入不全。Redis爲咱們提供了aof-load-truncated配置來兼容這種狀況,默認開啓。加載AOF時,當遇到此問題時會忽略並繼續啓動,同時打印警告日誌。

RDB-AOF混合持久化

        因爲RDB持久化和AOF持久化都有各自的優缺點,所以在很長一段時間裏,如何選擇合適的持久化方式成了不少Redis用戶面臨的一個難題。爲了解決這個問題,Redis從4.0版本開始引入RDB-AOF混合持久化模式,這種模式是基於AOF持久化模式構建而來的——若是用戶打開了服務的AOF持久化功能,而且將aof-use-rdb-preamble選項的值設置成了yes,那麼Redis服務器在執行AOF重寫操做時,就會像執行BGSAVE命令那樣,根據數據庫當前的狀態生成出相應的RDB數據,並將這些數據寫入新建的AOF文件中,至於那些在AOF重寫開始以後執行的Redis命令,則會繼續以協議文本的方式追加到新AOF文件的末尾,即已有的RDB數據的後面。換句話說,在開啓了RDB-AOF混合持久化功能以後,服務器生成的AOF文件將由兩個部分組成,其中位於AOF文件開頭的是RDB格式的數據,而跟在RDB數據後面的則是AOF格式的數據。

        經過使用RDB-AOF混合持久化功能,用戶能夠同時得到RDB持久化和AOF持久化的優勢:服務器既能夠經過AOF文件包含的RDB數據來實現快速的數據恢復操做,又能夠經過AOF文件包含的AOF數據來將丟失數據的時間窗口限制在1s以內。

        須要注意的是,由於RDB-AOF混合持久化生成的AOF文件會同時包含RDB格式的數據和AOF格式的數據,而傳統的AOF持久化只會生成包含AOF格式的數據,因此爲了不全新的RDB-AOF混合持久化功能給傳統的AOF持久化功能使用者帶來困惑,Redis目前默認是沒有打開RDB-AOF混合持久化功能的。可是Redis的做者聲稱,RDB-AOF混合持久化將在將來取代傳統的RDB持久化成爲Redis默認的持久化模式。

問題定位與優化

1.fork操做

        當Redis作RDB或AOF重寫時,一個必不可少的操做就是執行fork操做建立子進程,對於大多數操做系統來講fork是個重量級操做。雖然fork建立的子進程不須要拷貝父進程的物理內存空間,可是會複製父進程的空間內存頁表。例如對於10GB的Redis進程,須要複製大約20MB的內存頁表,所以fork操做耗時跟進程總內存量息息相關,若是使用虛擬化技術,特別是Xen虛擬機,fork操做會更耗時。

        對於高流量的Redis實例OPS可達5萬以上,若是fork操做耗時在秒級別將拖慢Redis幾萬條命令執行,對線上應用延遲影響很是明顯。正常狀況下fork耗時應該是每GB消耗20毫秒左右。

        如何改善fork操做的耗時:

        1)優先使用物理機或者高效支持fork操做的虛擬化技術,避免使用Xen。

        2)控制Redis實例最大可用內存,fork耗時跟內存量成正比,線上建議每一個Redis實例內存控制在10GB之內。

        3)合理配置Linux內存分配策略,避免物理內存不足致使fork失敗。

        4)下降fork操做的頻率,如適度放寬AOF自動觸發時機,避免沒必要要的全量複製等。

2.子進程開銷監控和優化

        子進程負責AOF或者RDB文件的重寫,它的運行過程主要涉及CPU、內存、硬盤三部分的消耗。

        1.CPU:子進程負責把進程內的數據分批寫入文件,這個過程屬於CPU密集操做,一般子進程對單核CPU利用率接近90%。

        1)Redis是CPU密集型服務,不要作綁定單核CPU操做。因爲子進程很是消耗CPU,會和父進程產生單核資源競爭。

        2)不要和其餘CPU密集型服務部署在一塊兒,形成CPU過分競爭。

        3)若是部署多個Redis實例,儘可能保證同一時刻只有一個子進程執行重寫工做

        2.內存:子進程經過fork操做產生,佔用內存大小等同於父進程,理論上須要兩倍的內存來完成持久化操做,但Linux有寫時複製機制(copy-on-write)。父子進程會共享相同的物理內存頁,當父進程處理寫請求時會把要修改的頁建立副本,而子進程在fork操做過程當中共享整個父進程內存快照。

        RDB重寫時,若是重寫過程當中存在內存修改操做,父進程負責建立所修改內存頁的副本,從日誌中能夠看出這部份內存消耗,能夠等價認爲RDB重寫消耗了此數量的內存

        AOF重寫時,父進程維護頁副本消耗同RDB重寫過程相似,不一樣之處在於AOF重寫須要AOF重寫緩衝區,所以根據日誌能夠預估內存消耗爲:父進程COW消耗的內存+AOF重寫緩衝區的內存,也就是AOF重寫時子進程消耗的內存量。

        1)同CPU優化同樣,若是部署多個Redis實例,儘可能保證同一時刻只有一個子進程在工做

        2)避免在大量寫入時作子進程重寫操做,這樣將致使父進程維護大量頁副本,形成內存消耗

        Linux kernel在2.6.38內核增長了Transparent Huge Pages(THP),支持huge page(2MB)的頁分配,默認開啓。當開啓時能夠下降fork建立子進程的速度,但執行fork以後,若是開啓THP,複製頁單位從原來4KB變爲2MB,會大幅增長重寫期間父進程內存消耗。建議設置「sudo echo never>/sys/kernel/mm/transparent_hugepage/enabled」關閉THP

        3.硬盤:子進程主要職責是把AOF或者RDB文件寫入硬盤持久化,勢必形成硬盤寫入壓力。根據Redis重寫AOF/RDB的數據量,結合系統工具如sar、iostat、iotop等,可分析出重寫期間硬盤負載狀況。

        1)不要和其餘高硬盤負載的服務部署在一塊兒。如:存儲服務、消息隊列服務等。

        2)AOF重寫時會消耗大量硬盤IO,能夠開啓配置no-appendfsync-on-rewrite,默認關閉。表示在AOF重寫期間不作fsync操做。

        3)當開啓AOF功能的Redis用於高流量寫入場景時,若是使用普通機械磁盤,寫入吞吐通常在100MB/s左右,這時Redis實例的瓶頸主要在AOF同步硬盤上,能夠換成固態硬盤加大吞吐量。

        4)對於單機配置多個Redis實例的狀況,能夠配置不一樣實例分盤存儲AOF文件,分攤硬盤寫入壓力

        配置no-appendfsync-on-rewrite=yes時,在極端狀況下可能丟失整個AOF重寫期間的數據,須要根據數據安全性決定是否配置。

3.AOF追加阻塞

        當開啓AOF持久化時,經常使用的同步硬盤的策略是everysec,用於平衡性能和數據安全性。對於這種方式,Redis使用另外一條線程每秒執行fsync同步硬盤。當系統硬盤資源繁忙時,會形成Redis主線程阻塞。

        1)主線程負責寫入AOF緩衝區。

        2)AOF線程負責每秒執行一次同步磁盤操做,並記錄最近一次同步時間。

        3)主線程負責對比上次AOF同步時間:

        ·若是距上次同步成功時間在2秒內,主線程直接返回。

        ·若是距上次同步成功時間超過2秒,主線程將會阻塞,直到同步操做完成。

        經過對AOF阻塞流程能夠發現兩個問題:

        1)everysec配置最多可能丟失2秒數據,不是1秒

        2)若是系統fsync緩慢,將會致使Redis主線程阻塞影響效率

        發生AOF阻塞時,Redis輸出以下日誌,用於記錄AOF fsync阻塞致使拖慢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

        每當發生AOF追加阻塞事件發生時,在info Persistence統計中,aof_delayed_fsync指標會累加,查看這個指標方便定位AOF阻塞問題。AOF同步最多容許2秒的延遲,當延遲發生時說明硬盤存在高負載問題,能夠經過監控工具如iotop,定位消耗硬盤IO資源的進程。優化AOF追加阻塞問題主要是優化系統硬盤負載,優化方式見上一節。

多實例部署

        Redis單線程架構致使沒法充分利用CPU多核特性,一般的作法是在一臺機器上部署多個Redis實例。當多個實例開啓AOF重寫後,彼此之間會產生對CPU和IO的競爭。

        對於單機多Redis部署,若是同一時刻運行多個子進程,對當前系統影響將很是明顯,所以須要採用一種措施,把子進程工做進行隔離。Redis在info Persistence中爲咱們提供了監控子進程運行情況的度量指標,如表所示:

        咱們基於以上指標,能夠經過外部程序輪詢控制AOF重寫操做的執行,整個過程如圖所示。

        1)外部程序定時輪詢監控機器(machine)上全部Redis實例。

        2)對於開啓AOF的實例,查看(aof_current_size-aof_base_size)/aof_base_size確認增加率。

        3)當增加率超過特定閾值(如100%),執行bgrewriteaof命令手動觸發當前實例的AOF重寫。

        4)運行期間循環檢查aof_rewrite_in_progress和aof_current_rewrite_time_sec指標,直到AOF重寫結束。

        5)確認實例AOF重寫完成後,再檢查其餘實例並重復2)~4)步操做。從而保證機器內每一個Redis實例AOF重寫串行化執行。

相關文章
相關標籤/搜索