1、主從複製詳述html
在前面的兩篇文章中,分別介紹了Redis的內存模型和Redis的持久化。前端
在Redis的持久化中曾提到,Redis高可用的方案包括持久化、主從複製(及讀寫分離)、哨兵和集羣。其中持久化側重解決的是Redis數據的單機備份問題(從內存到硬盤的備份);而主從複製則側重解決數據的多機熱備。此外,主從複製還能夠實現負載均衡和故障恢復。node
這篇文章中,將詳細介紹Redis主從複製的方方面面,包括:如何使用主從複製、主從複製的原理(重點是全量複製和部分複製、以及心跳機制)、實際應用中須要注意的問題(如數據不一致問題、複製超時問題、複製緩衝區溢出問題)、主從複製相關的配置(重點是repl-timeout、client-output-buffer-limit slave)等。ios
主從複製,是指將一臺Redis服務器的數據,複製到其餘的Redis服務器。前者稱爲主節點(master),後者稱爲從節點(slave);數據的複製是單向的,只能由主節點到從節點。git
默認狀況下,每臺Redis服務器都是主節點;且一個主節點能夠有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。es6
主從複製的做用github
主從複製的做用主要包括:web
爲了更直觀的理解主從複製,在介紹其內部原理以前,先說明咱們須要如何操做才能開啓主從複製。redis
須要注意,主從複製的開啓,徹底是在從節點發起的;不須要咱們在主節點作任何事情。算法
從節點開啓主從複製,有3種方式:
(1)配置文件
在從服務器的配置文件中加入:slaveof <masterip> <masterport>
(2)啓動命令
redis-server啓動命令後加入 --slaveof <masterip> <masterport>
(3)客戶端命令
Redis服務器啓動後,直接經過客戶端執行命令:slaveof <masterip> <masterport>,則該Redis實例成爲從節點。
上述3種方式是等效的,下面以客戶端命令的方式爲例,看一下當執行了slaveof後,Redis主節點和從節點的變化。
方便起見,實驗所使用的主從節點是在一臺機器上的不一樣Redis實例,其中主節點監聽6379端口,從節點監聽6380端口;從節點監聽的端口號能夠在配置文件中修改:
啓動後能夠看到:
兩個Redis節點啓動後(分別稱爲6379節點和6380節點),默認都是主節點。
此時在6380節點執行slaveof命令,使之變爲從節點:
下面驗證一下,在主從複製創建後,主節點的數據會複製到從節點中。
(1)首先在從節點查詢一個不存在的key:
(2)而後在主節點中增長這個key:
(3)此時在從節點中再次查詢這個key,會發現主節點的操做已經同步至從節點:
(4)而後在主節點刪除這個key:
(5)此時在從節點中再次查詢這個key,會發現主節點的操做已經同步至從節點:
經過slaveof <masterip> <masterport>命令創建主從複製關係之後,能夠經過slaveof no one斷開。須要注意的是,從節點斷開復制後,不會刪除已有的數據,只是再也不接受主節點新的數據變化。
從節點執行slaveof no one後,打印日誌以下所示;能夠看出斷開復制後,從節點又變回爲主節點。
主節點打印日誌以下:
上面一節中,介紹瞭如何操做能夠創建主從關係;本小節將介紹主從複製的實現原理。
主從複製過程大致能夠分爲3個階段:鏈接創建階段(即準備階段)、數據同步階段、命令傳播階段;下面分別進行介紹。
該階段的主要做用是在主從節點之間創建鏈接,爲數據同步作好準備。
從節點服務器內部維護了兩個字段,即masterhost和masterport字段,用於存儲主節點的ip和port信息。
須要注意的是,slaveof是異步命令,從節點完成主節點ip和port的保存後,向發送slaveof命令的客戶端直接返回OK,實際的複製操做在這以後纔開始進行。
這個過程當中,能夠看到從節點打印日誌以下:
從節點每秒1次調用複製定時函數replicationCron(),若是發現了有主節點能夠鏈接,便會根據主節點的ip和port,建立socket鏈接。若是鏈接成功,則:
從節點:爲該socket創建一個專門處理複製工做的文件事件處理器,負責後續的複製工做,如接收RDB文件、接收命令傳播等。
主節點:接收到從節點的socket鏈接後(即accept以後),爲該socket建立相應的客戶端狀態,並將從節點看作是鏈接到主節點的一個客戶端,後面的步驟會以從節點向主節點發送命令請求的形式來進行。
這個過程當中,從節點打印日誌以下:
從節點成爲主節點的客戶端以後,發送ping命令進行首次請求,目的是:檢查socket鏈接是否可用,以及主節點當前是否可以處理請求。
從節點發送ping命令後,可能出現3種狀況:
(1)返回pong:說明socket鏈接正常,且主節點當前能夠處理請求,複製過程繼續。
(2)超時:必定時間後從節點仍未收到主節點的回覆,說明socket鏈接不可用,則從節點斷開socket鏈接,並重連。
(3)返回pong之外的結果:若是主節點返回其餘結果,如正在處理超時運行的腳本,說明主節點當前沒法處理命令,則從節點斷開socket鏈接,並重連。
在主節點返回pong狀況下,從節點打印日誌以下:
若是從節點中設置了masterauth選項,則從節點須要向主節點進行身份驗證;沒有設置該選項,則不須要驗證。從節點進行身份驗證是經過向主節點發送auth命令進行的,auth命令的參數即爲配置文件中的masterauth的值。
若是主節點設置密碼的狀態,與從節點masterauth的狀態一致(一致是指都存在,且密碼相同,或者都不存在),則身份驗證經過,複製過程繼續;若是不一致,則從節點斷開socket鏈接,並重連。
身份驗證以後,從節點會向主節點發送其監聽的端口號(前述例子中爲6380),主節點將該信息保存到該從節點對應的客戶端的slave_listening_port字段中;該端口信息除了在主節點中執行info Replication時顯示之外,沒有其餘做用。
主從節點之間的鏈接創建之後,即可以開始進行數據同步,該階段能夠理解爲從節點數據的初始化。具體執行的方式是:從節點向主節點發送psync命令(Redis2.8之前是sync命令),開始同步。
數據同步階段是主從複製最核心的階段,根據主從節點當前狀態的不一樣,能夠分爲全量複製和部分複製,下面會有一章專門講解這兩種複製方式以及psync命令的執行過程,這裏再也不詳述。
須要注意的是,在數據同步階段以前,從節點是主節點的客戶端,主節點不是從節點的客戶端;而到了這一階段及之後,主從節點互爲客戶端。緣由在於:在此以前,主節點只須要響應從節點的請求便可,不須要主動發請求,而在數據同步階段和後面的命令傳播階段,主節點須要主動向從節點發送請求(如推送緩衝區中的寫命令),才能完成複製。
數據同步階段完成後,主從節點進入命令傳播階段;在這個階段主節點將本身執行的寫命令發送給從節點,從節點接收命令並執行,從而保證主從節點數據的一致性。
在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING和REPLCONF ACK。因爲心跳機制的原理涉及部分複製,所以將在介紹了部分複製的相關內容後單獨介紹該心跳機制。
延遲與不一致
須要注意的是,命令傳播是異步的過程,即主節點發送寫命令後並不會等待從節點的回覆;所以實際上主從節點之間很難保持實時的一致性,延遲在所不免。數據不一致的程度,與主從節點之間的網絡情況、主節點寫命令的執行頻率、以及主節點中的repl-disable-tcp-nodelay配置等有關。
repl-disable-tcp-nodelay no:該配置做用於命令傳播階段,控制主節點是否禁止與從節點的TCP_NODELAY;默認no,即不由止TCP_NODELAY。當設置爲yes時,TCP會對包進行合併從而減小帶寬,可是發送的頻率會下降,從節點數據延遲增長,一致性變差;具體發送頻率與Linux內核的配置有關,默認配置爲40ms。當設置爲no時,TCP會立馬將主節點的數據發送給從節點,帶寬增長但延遲變小。
通常來講,只有當應用對Redis數據不一致的容忍度較高,且主從節點之間網絡情況很差時,纔會設置爲yes;多數狀況使用默認值no。
在Redis2.8之前,從節點向主節點發送sync命令請求同步數據,此時的同步方式是全量複製;在Redis2.8及之後,從節點能夠發送psync命令請求同步數據,此時根據主從節點當前狀態的不一樣,同步方式多是全量複製或部分複製。後文介紹以Redis2.8及之後版本爲例。
Redis經過psync命令進行全量複製的過程以下:
(1)從節點判斷沒法進行部分複製,向主節點發送全量複製的請求;或從節點發送部分複製的請求,但主節點判斷沒法進行全量複製;具體判斷過程須要在講述了部分複製原理後再介紹。
(2)主節點收到全量複製的命令後,執行bgsave,在後臺生成RDB文件,並使用一個緩衝區(稱爲複製緩衝區)記錄從如今開始執行的全部寫命令
(3)主節點的bgsave執行完成後,將RDB文件發送給從節點;從節點首先清除本身的舊數據,而後載入接收的RDB文件,將數據庫狀態更新至主節點執行bgsave時的數據庫狀態
(4)主節點將前述複製緩衝區中的全部寫命令發送給從節點,從節點執行這些寫命令,將數據庫狀態更新至主節點的最新狀態
(5)若是從節點開啓了AOF,則會觸發bgrewriteaof的執行,從而保證AOF文件更新至主節點的最新狀態
下面是執行全量複製時,主從節點打印的日誌;能夠看出日誌內容與上述步驟是徹底對應的。
主節點的打印日誌以下:
從節點打印日誌以下圖所示:
其中,有幾點須要注意:從節點接收了來自主節點的89260個字節的數據;從節點在載入主節點的數據以前要先將老數據清除;從節點在同步完數據後,調用了bgrewriteaof。
經過全量複製的過程能夠看出,全量複製是很是重型的操做:
(1)主節點經過bgsave命令fork子進程進行RDB持久化,該過程是很是消耗CPU、內存(頁表複製)、硬盤IO的;關於bgsave的性能問題,能夠參考 深刻學習Redis(2):持久化
(2)主節點經過網絡將RDB文件發送給從節點,對主從節點的帶寬都會帶來很大的消耗
(3)從節點清空老數據、載入新RDB文件的過程是阻塞的,沒法響應客戶端的命令;若是從節點執行bgrewriteaof,也會帶來額外的消耗
因爲全量複製在主節點數據量較大時效率過低,所以Redis2.8開始提供部分複製,用於處理網絡中斷時的數據同步。
部分複製的實現,依賴於三個重要的概念:
主節點和從節點分別維護一個複製偏移量(offset),表明的是主節點向從節點傳遞的字節數;主節點每次向從節點傳播N個字節數據時,主節點的offset增長N;從節點每次收到主節點傳來的N個字節數據時,從節點的offset增長N。
offset用於判斷主從節點的數據庫狀態是否一致:若是兩者offset相同,則一致;若是offset不一樣,則不一致,此時能夠根據兩個offset找出從節點缺乏的那部分數據。例如,若是主節點的offset是1000,而從節點的offset是500,那麼部分複製就須要將offset爲501-1000的數據傳遞給從節點。而offset爲501-1000的數據存儲的位置,就是下面要介紹的複製積壓緩衝區。
複製積壓緩衝區是由主節點維護的、固定長度的、先進先出(FIFO)隊列,默認大小1MB;當主節點開始有從節點時建立,其做用是備份主節點最近發送給從節點的數據。注意,不管主節點有一個仍是多個從節點,都只須要一個複製積壓緩衝區。
在命令傳播階段,主節點除了將寫命令發送給從節點,還會發送一份給複製積壓緩衝區,做爲寫命令的備份;除了存儲寫命令,複製積壓緩衝區中還存儲了其中的每一個字節對應的複製偏移量(offset)。因爲複製積壓緩衝區定長且是先進先出,因此它保存的是主節點最近執行的寫命令;時間較早的寫命令會被擠出緩衝區。
因爲該緩衝區長度固定且有限,所以能夠備份的寫命令也有限,當主從節點offset的差距過大超過緩衝區長度時,將沒法執行部分複製,只能執行全量複製。反過來講,爲了提升網絡中斷時部分複製執行的機率,能夠根據須要增大複製積壓緩衝區的大小(經過配置repl-backlog-size);例如若是網絡中斷的平均時間是60s,而主節點平均每秒產生的寫命令(特定協議格式)所佔的字節數爲100KB,則複製積壓緩衝區的平均需求爲6MB,保險起見,能夠設置爲12MB,來保證絕大多數斷線狀況均可以使用部分複製。
從節點將offset發送給主節點後,主節點根據offset和緩衝區大小決定可否執行部分複製:
每一個Redis節點(不管主從),在啓動時都會自動生成一個隨機ID(每次啓動都不同),由40個隨機的十六進制字符組成;runid用來惟一識別一個Redis節點。經過info Server命令,能夠查看節點的runid:
主從節點初次複製時,主節點將本身的runid發送給從節點,從節點將這個runid保存起來;當斷線重連時,從節點會將這個runid發送給主節點;主節點根據runid判斷可否進行部分複製:
在瞭解了複製偏移量、複製積壓緩衝區、節點運行id以後,本節將介紹psync命令的參數和返回值,從而說明psync命令執行過程當中,主從節點是如何肯定使用全量複製仍是部分複製的。
psync命令的執行過程能夠參見下圖(圖片來源:《Redis設計與實現》):
(1)首先,從節點根據當前狀態,決定如何調用psync命令:
(2)主節點根據收到的psync命令,及當前服務器狀態,決定執行全量複製仍是部分複製:
在下面的演示中,網絡中斷幾分鐘後恢復,斷開鏈接的主從節點進行了部分複製;爲了便於模擬網絡中斷,本例中的主從節點在局域網中的兩臺機器上。
網絡中斷
網絡中斷一段時間後,主節點和從節點都會發現失去了與對方的鏈接(關於主從節點對超時的判斷機制,後面會有說明);此後,從節點便開始執行對主節點的重連,因爲此時網絡尚未恢復,重連失敗,從節點會一直嘗試重連。
主節點日誌以下:
從節點日誌以下:
網絡恢復
網絡恢復後,從節點鏈接主節點成功,並請求進行部分複製,主節點接收請求後,兩者進行部分複製以同步數據。
主節點日誌以下:
從節點日誌以下:
在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING和REPLCONF ACK。心跳機制對於主從複製的超時判斷、數據安全等有做用。
每隔指定的時間,主節點會向從節點發送PING命令,這個PING命令的做用,主要是爲了讓從節點進行超時判斷。
PING發送的頻率由repl-ping-slave-period參數控制,單位是秒,默認值是10s。
關於該PING命令到底是由主節點發給從節點,仍是相反,有一些爭議;由於在Redis的官方文檔中,對該參數的註釋中說明是從節點向主節點發送PING命令,以下圖所示:
可是根據該參數的名稱(含有ping-slave),以及代碼實現,我認爲該PING命令是主節點發給從節點的。相關代碼以下:
在命令傳播階段,從節點會向主節點發送REPLCONF ACK命令,頻率是每秒1次;命令格式爲:REPLCONF ACK {offset},其中offset指從節點保存的複製偏移量。REPLCONF ACK命令的做用包括:
(1)實時監測主從節點網絡狀態:該命令會被主節點用於複製超時的判斷。此外,在主節點中使用info Replication,能夠看到其從節點的狀態中的lag值,表明的是主節點上次收到該REPLCONF ACK命令的時間間隔,在正常狀況下,該值應該是0或1,以下圖所示:
(2)檢測命令丟失:從節點發送了自身的offset,主節點會與本身的offset對比,若是從節點數據缺失(如網絡丟包),主節點會推送缺失的數據(這裏也會利用複製積壓緩衝區)。注意,offset和複製積壓緩衝區,不只能夠用於部分複製,也能夠用於處理命令丟失等情形;區別在於前者是在斷線重連後進行的,然後者是在主從節點沒有斷線的狀況下進行的。
(3)輔助保證從節點的數量和延遲:Redis主節點中使用min-slaves-to-write和min-slaves-max-lag參數,來保證主節點在不安全的狀況下不會執行寫命令;所謂不安全,是指從節點數量太少,或延遲太高。例如min-slaves-to-write和min-slaves-max-lag分別是3和10,含義是若是從節點數量小於3個,或全部從節點的延遲值都大於10s,則主節點拒絕執行寫命令。而這裏從節點延遲值的獲取,就是經過主節點接收到REPLCONF ACK命令的時間來判斷的,即前面所說的info Replication中的lag值。
在主從複製基礎上實現的讀寫分離,能夠實現Redis的讀負載均衡:由主節點提供寫服務,由一個或多個從節點提供讀服務(多個從節點既能夠提升數據冗餘程度,也能夠最大化讀負載能力);在讀負載較大的應用場景下,能夠大大提升Redis服務器的併發量。下面介紹在使用Redis讀寫分離時,須要注意的問題。
前面已經講到,因爲主從複製的命令傳播是異步的,延遲與數據的不一致不可避免。若是應用對數據不一致的接受程度程度較低,可能的優化措施包括:優化主從節點之間的網絡環境(如在同機房部署);監控主從節點延遲(經過offset)判斷,若是從節點延遲過大,通知應用再也不經過該從節點讀取數據;使用集羣同時擴展寫負載和讀負載等。
在命令傳播階段之外的其餘狀況下,從節點的數據不一致可能更加嚴重,例如鏈接在數據同步階段,或從節點失去與主節點的鏈接時等。從節點的slave-serve-stale-data參數便與此有關:它控制這種狀況下從節點的表現;若是爲yes(默認值),則從節點仍可以響應客戶端的命令,若是爲no,則從節點只能響應info、slaveof等少數命令。該參數的設置與應用對數據一致性的要求有關;若是對數據一致性要求很高,則應設置爲no。
在單機版Redis中,存在兩種刪除策略:
在主從複製場景下,爲了主從節點的數據一致性,從節點不會主動刪除數據,而是由主節點控制從節點中過時數據的刪除。因爲主節點的惰性刪除和按期刪除策略,都不能保證主節點及時對過時數據執行刪除操做,所以,當客戶端經過Redis從節點讀取數據時,很容易讀取到已通過期的數據。
Redis 3.2中,從節點在讀取數據時,增長了對數據是否過時的判斷:若是該數據已過時,則不返回給客戶端;將Redis升級到3.2能夠解決數據過時問題。
在沒有使用哨兵的讀寫分離場景下,應用針對讀和寫分別鏈接不一樣的Redis節點;當主節點或從節點出現問題而發生更改時,須要及時修改應用程序讀寫Redis數據的鏈接;鏈接的切換能夠手動進行,或者本身寫監控程序進行切換,但前者響應慢、容易出錯,後者實現複雜,成本都不算低。
在使用讀寫分離以前,能夠考慮其餘方法增長Redis的讀負載能力:如儘可能優化主節點(減小慢查詢、減小持久化等其餘狀況帶來的阻塞等)提升負載能力;使用Redis集羣同時提升讀負載能力和寫負載能力等。若是使用讀寫分離,能夠使用哨兵(也可採用redis cluster),使主從節點的故障切換儘量自動化,並減小對應用程序的侵入。
主從節點複製超時是致使複製中斷的最重要的緣由之一,本小節單獨說明超時問題,下一小節說明其餘會致使複製中斷的問題。
超時判斷意義
在複製鏈接創建過程當中及以後,主從節點都有機制判斷鏈接是否超時,其意義在於:
(1)若是主節點判斷鏈接超時,其會釋放相應從節點的鏈接,從而釋放各類資源,不然無效的從節點仍會佔用主節點的各類資源(輸出緩衝區、帶寬、鏈接等);此外鏈接超時的判斷可讓主節點更準確的知道當前有效從節點的個數,有助於保證數據安全(配合前面講到的min-slaves-to-write等參數)。
(2)若是從節點判斷鏈接超時,則能夠及時從新創建鏈接,避免與主節點數據長期的不一致。
判斷機制
主從複製超時判斷的核心,在於repl-timeout參數,該參數規定了超時時間的閾值(默認60s),對於主節點和從節點同時有效;主從節點觸發超時的條件分別以下:
(1)主節點:每秒1次調用複製定時函數replicationCron(),在其中判斷當前時間距離上次收到各個從節點REPLCONF ACK的時間,是否超過了repl-timeout值,若是超過了則釋放相應從節點的鏈接。
(2)從節點:從節點對超時的判斷一樣是在複製定時函數中判斷,基本邏輯是:
主從節點判斷鏈接超時的相關源代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
/* Replication cron function, called 1 time per second. */
void
replicationCron(
void
) {
static
long
long
replication_cron_loops = 0;
/* Non blocking connection timeout? */
if
(server.masterhost &&
(server.repl_state == REDIS_REPL_CONNECTING ||
slaveIsInHandshakeState()) &&
(
time
(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
{
redisLog(REDIS_WARNING,
"Timeout connecting to the MASTER..."
);
undoConnectWithMaster();
}
/* Bulk transfer I/O timeout? */
if
(server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&
(
time
(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
{
redisLog(REDIS_WARNING,
"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value."
);
replicationAbortSyncTransfer();
}
/* Timed out master when we are an already connected slave? */
if
(server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&
(
time
(NULL)-server.master->lastinteraction) > server.repl_timeout)
{
redisLog(REDIS_WARNING,
"MASTER timeout: no data nor PING received..."
);
freeClient(server.master);
}
//此處省略無關代碼……
/* Disconnect timedout slaves. */
if
(listLength(server.slaves)) {
listIter li;
listNode *ln;
listRewind(server.slaves,&li);
while
((ln = listNext(&li))) {
redisClient *slave = ln->value;
if
(slave->replstate != REDIS_REPL_ONLINE)
continue
;
if
(slave->flags & REDIS_PRE_PSYNC)
continue
;
if
((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
{
redisLog(REDIS_WARNING,
"Disconnecting timedout slave: %s"
,
replicationGetSlaveName(slave));
freeClient(slave);
}
}
}
//此處省略無關代碼……
}
|
須要注意的坑
下面介紹與複製階段鏈接超時有關的一些實際問題:
(1)數據同步階段:在主從節點進行全量複製bgsave時,主節點須要首先fork子進程將當前數據保存到RDB文件中,而後再將RDB文件經過網絡傳輸到從節點。若是RDB文件過大,主節點在fork子進程+保存RDB文件時耗時過多,可能會致使從節點長時間收不到數據而觸發超時;此時從節點會重連主節點,而後再次全量複製,再次超時,再次重連……這是個悲傷的循環。爲了不這種狀況的發生,除了注意Redis單機數據量不要過大,另外一方面就是適當增大repl-timeout值,具體的大小能夠根據bgsave耗時來調整。
(2)命令傳播階段:如前所述,在該階段主節點會向從節點發送PING命令,頻率由repl-ping-slave-period控制;該參數應明顯小於repl-timeout值(後者至少是前者的幾倍)。不然,若是兩個參數相等或接近,網絡抖動致使個別PING命令丟失,此時恰巧主節點也沒有向從節點發送數據,則從節點很容易判斷超時。
(3)慢查詢致使的阻塞:若是主節點或從節點執行了一些慢查詢(如keys *或者對大數據的hgetall等),致使服務器阻塞;阻塞期間沒法響應複製鏈接中對方節點的請求,可能致使複製超時。
主從節點超時是複製中斷的緣由之一,除此以外,還有其餘狀況可能致使複製中斷,其中最主要的是複製緩衝區溢出問題。
前面曾提到過,在全量複製階段,主節點會將執行的寫命令放到複製緩衝區中,該緩衝區存放的數據包括瞭如下幾個時間段內主節點執行的寫命令:bgsave生成RDB文件、RDB文件由主節點發往從節點、從節點清空老數據並載入RDB文件中的數據。當主節點數據量較大,或者主從節點之間網絡延遲較大時,可能致使該緩衝區的大小超過了限制,此時主節點會斷開與從節點之間的鏈接;這種狀況可能引發全量複製->複製緩衝區溢出致使鏈接中斷->重連->全量複製->複製緩衝區溢出致使鏈接中斷……的循環。
複製緩衝區的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默認值爲client-output-buffer-limit slave 256MB 64MB 60,其含義是:若是buffer大於256MB,或者連續60s大於64MB,則主節點會斷開與該從節點的鏈接。該參數是能夠經過config set命令動態配置的(即不重啓Redis也能夠生效)。
當複製緩衝區溢出時,主節點打印日誌以下所示:
須要注意的是,複製緩衝區是客戶端輸出緩衝區的一種,主節點會爲每個從節點分別分配複製緩衝區;而複製積壓緩衝區則是一個主節點只有一個,不管它有多少個從節點。
在介紹了Redis複製的種種細節以後,如今咱們能夠來總結一下,在下面常見的場景中,什麼時候使用部分複製,以及須要注意哪些問題。
此時全量複製不可避免,但仍有幾點須要注意:若是主節點的數據量較大,應該儘可能避開流量的高峯期,避免形成阻塞;若是有多個從節點須要創建對主節點的複製,能夠考慮將幾個從節點錯開,避免主節點帶寬佔用過大。此外,若是從節點過多,也能夠調整主從複製的拓撲結構,由一主多從結構變爲樹狀結構(中間的節點既是其主節點的從節點,也是其從節點的主節點);但使用樹狀結構應該謹慎:雖然主節點的直接從節點減小,下降了主節點的負擔,可是多層從節點的延遲增大,數據一致性變差;且結構複雜,維護至關困難。
主節點重啓能夠分爲兩種狀況來討論,一種是故障致使宕機,另外一種則是有計劃的重啓。
主節點宕機
主節點宕機重啓後,runid會發生變化,所以不能進行部分複製,只能全量複製。
實際上在主節點宕機的狀況下,應進行故障轉移處理,將其中的一個從節點升級爲主節點,其餘從節點重新的主節點進行復制;且故障轉移應儘可能的自動化,後面文章將要介紹的哨兵即可以進行自動的故障轉移。
安全重啓:debug reload
在一些場景下,可能但願對主節點進行重啓,例如主節點內存碎片率太高,或者但願調整一些只能在啓動時調整的參數。若是使用普通的手段重啓主節點,會使得runid發生變化,可能致使沒必要要的全量複製。
爲了解決這個問題,Redis提供了debug reload的重啓方式:重啓後,主節點的runid和offset都不受影響,避免了全量複製。
以下圖所示,debug reload重啓後runid和offset都未受影響:
但debug reload是一柄雙刃劍:它會清空當前內存中的數據,從新從RDB文件中加載,這個過程會致使主節點的阻塞,所以也須要謹慎。
從節點宕機重啓後,其保存的主節點的runid會丟失,所以即便再次執行slaveof,也沒法進行部分複製。
若是主從節點之間出現網絡問題,形成短期內網絡中斷,能夠分爲多種狀況討論。
第一種狀況:網絡問題時間極爲短暫,只形成了短暫的丟包,主從節點都沒有斷定超時(未觸發repl-timeout);此時只須要經過REPLCONF ACK來補充丟失的數據便可。
第二種狀況:網絡問題時間很長,主從節點判斷超時(觸發了repl-timeout),且丟失的數據過多,超過了複製積壓緩衝區所能存儲的範圍;此時主從節點沒法進行部分複製,只能進行全量複製。爲了儘量避免這種狀況的發生,應該根據實際狀況適當調整複製積壓緩衝區的大小;此外及時發現並修復網絡中斷,也能夠減小全量複製。
第三種狀況:介於前述兩種狀況之間,主從節點判斷超時,且丟失的數據仍然都在複製積壓緩衝區中;此時主從節點能夠進行部分複製。
這一節總結一下與複製有關的配置,說明這些配置的做用、起做用的階段,以及配置方法等;經過了解這些配置,一方面加深對Redis複製的瞭解,另外一方面掌握這些配置的方法,能夠優化Redis的使用,少走坑。
配置大體能夠分爲主節點相關配置、從節點相關配置以及與主從節點都有關的配置,下面分別說明。
首先介紹最特殊的配置,它決定了該節點是主節點仍是從節點:
1) slaveof <masterip> <masterport>:Redis啓動時起做用;做用是創建複製關係,開啓了該配置的Redis服務器在啓動後成爲從節點。該註釋默認註釋掉,即Redis服務器默認都是主節點。
2) repl-timeout 60:與各個階段主從節點鏈接超時判斷有關,見前面的介紹。
1) repl-diskless-sync no:做用於全量複製階段,控制主節點是否使用diskless複製(無盤複製)。所謂diskless複製,是指在全量複製時,主節點再也不先把數據寫入RDB文件,而是直接寫入slave的socket中,整個過程當中不涉及硬盤;diskless複製在磁盤IO很慢而網速很快時更有優點。須要注意的是,截至Redis3.0,diskless複製處於實驗階段,默認是關閉的。
2) repl-diskless-sync-delay 5:該配置做用於全量複製階段,當主節點使用diskless複製時,該配置決定主節點向從節點發送以前停頓的時間,單位是秒;只有當diskless複製打開時有效,默認5s。之因此設置停頓時間,是基於如下兩個考慮:(1)向slave的socket的傳輸一旦開始,新鏈接的slave只能等待當前數據傳輸結束,才能開始新的數據傳輸 (2)多個從節點有較大的機率在短期內創建主從複製。
3) client-output-buffer-limit slave 256MB 64MB 60:與全量複製階段主節點的緩衝區大小有關,見前面的介紹。
4) repl-disable-tcp-nodelay no:與命令傳播階段的延遲有關,見前面的介紹。
5) masterauth <master-password>:與鏈接創建階段的身份驗證有關,見前面的介紹。
6) repl-ping-slave-period 10:與命令傳播階段主從節點的超時判斷有關,見前面的介紹。
7) repl-backlog-size 1mb:複製積壓緩衝區的大小,見前面的介紹。
8) repl-backlog-ttl 3600:當主節點沒有從節點時,複製積壓緩衝區保留的時間,這樣當斷開的從節點從新連進來時,能夠進行全量複製;默認3600s。若是設置爲0,則永遠不會釋放複製積壓緩衝區。
9) min-slaves-to-write 3與min-slaves-max-lag 10:規定了主節點的最小從節點數目,及對應的最大延遲,見前面的介紹。
1) slave-serve-stale-data yes:與從節點數據陳舊時是否響應客戶端命令有關,見前面的介紹。
2) slave-read-only yes:從節點是否只讀;默認是隻讀的。因爲從節點開啓寫操做容易致使主從節點的數據不一致,所以該配置儘可能不要修改。
在 深刻學習Redis(2):持久化 一文中,講到了fork操做對Redis單機內存大小的限制。實際上在Redis的使用中,限制單機內存大小的因素很是之多,下面總結一下在主從複製中,單機內存過大可能形成的影響:
(1)切主:當主節點宕機時,一種常見的容災策略是將其中一個從節點提高爲主節點,並將其餘從節點掛載到新的主節點上,此時這些從節點只能進行全量複製;若是Redis單機內存達到10GB,一個從節點的同步時間在幾分鐘的級別;若是從節點較多,恢復的速度會更慢。若是系統的讀負載很高,而這段時間從節點沒法提供服務,會對系統形成很大的壓力。
(2)從庫擴容:若是訪問量忽然增大,此時但願增長從節點分擔讀負載,若是數據量過大,從節點同步太慢,難以及時應對訪問量的暴增。
(3)緩衝區溢出:(1)和(2)都是從節點能夠正常同步的情形(雖然慢),可是若是數據量過大,致使全量複製階段主節點的複製緩衝區溢出,從而致使複製中斷,則主從節點的數據同步會全量複製->複製緩衝區溢出致使複製中斷->重連->全量複製->複製緩衝區溢出致使複製中斷……的循環。
(4)超時:若是數據量過大,全量複製階段主節點fork+保存RDB文件耗時過大,從節點長時間接收不到數據觸發超時,主從節點的數據同步一樣可能陷入全量複製->超時致使複製中斷->重連->全量複製->超時致使複製中斷……的循環。
此外,主節點單機內存除了絕對量不能太大,其佔用主機內存的比例也不該過大:最好只使用50%-65%的內存,留下30%-45%的內存用於執行bgsave命令和建立複製緩衝區等。
在Redis客戶端經過info Replication能夠查看與複製相關的狀態,對於瞭解主從節點的當前狀態,以及解決出現的問題都會有幫助。
主節點:
從節點:
對於從節點,上半部分展現的是其做爲從節點的狀態,從connectd_slaves開始,展現的是其做爲潛在的主節點的狀態。
info Replication中展現的大部份內容在文章中都已經講述,這裏再也不詳述。
下面回顧一下本文的主要內容:
一、主從複製的做用:宏觀的瞭解主從複製是爲了解決什麼樣的問題,即數據冗餘、故障恢復、讀負載均衡等。
二、主從複製的操做:即slaveof命令。
三、主從複製的原理:主從複製包括了鏈接創建階段、數據同步階段、命令傳播階段;其中數據同步階段,有全量複製和部分複製兩種數據同步方式;命令傳播階段,主從節點之間有PING和REPLCONF ACK命令互相進行心跳檢測。
四、應用中的問題:包括讀寫分離的問題(數據不一致問題、數據過時問題、故障切換問題等)、複製超時問題、複製中斷問題等,而後總結了主從複製相關的配置,其中repl-timeout、client-output-buffer-limit slave等對解決Redis主從複製中出現的問題可能會有幫助。
主從複製雖然解決或緩解了數據冗餘、故障恢復、讀負載均衡等問題,但其缺陷仍很明顯:故障恢復沒法自動化;寫操做沒法負載均衡;存儲能力受到單機的限制;這些問題的解決,須要哨兵和集羣的幫助,我將在後面的文章中介紹,歡迎關注。
《Redis開發與運維》
《Redis設計與實現》
《Redis實戰》
http://mdba.cn/2015/03/16/redis複製中斷問題-慢查詢/
https://redislabs.com/blog/top-redis-headaches-for-devops-replication-buffer/
http://mdba.cn/2015/03/17/redis主從複製(2)-replication-buffer與replication-backlog/
https://github.com/antirez/redis/issues/918
https://blog.csdn.net/qbw2010/article/details/50496982
https://mp.weixin.qq.com/s?__biz=MzIxMzEzMjM5NQ==&mid=2651029484&idx=1&sn=5882f4c7c390a0a0e4f6dfd872e203b5&chksm=8c4caae8bb3b23fe77909e307d45a071186f55069e5207602c61383eab573885615c1d835904&mpshare=1&scene=1&srcid=0327SokqtxEY3WojWNDMHLYl#rd
做者:編程迷思
出處:https://www.cnblogs.com/kismetv/p/9137897.html
「
在上一篇文章中,介紹了《Redis的內存模型》,從這篇文章開始,將依次介紹 Redis 高可用相關的知識——持久化、複製(及讀寫分離)、哨兵、以及集羣。
本文將先說明上述幾種技術分別解決了 Redis 高可用的什麼問題,而後詳細介紹 Redis 的持久化技術,主要是 RDB 和 AOF 兩種持久化方案。
在介紹 RDB 和 AOF 方案時,不只介紹它的做用及操做方法,同時介紹持久化實現的一些原理細節及須要注意的問題。最後,介紹在實際使用中,持久化方案的選擇,以及常常遇到的問題等。
下面分別從如下幾個方面講解:
Redis 高可用概述
Redis 持久化概述
RDB 持久化
AOF 持久化
方案選擇與常見問題
總結
Redis 高可用概述
在介紹 Redis 高可用以前,先說明一下在 Redis 的語境中高可用的含義。在 Web 服務器中,高可用是指服務器能夠正常訪問的時間,衡量的標準是在多長時間內能夠提供正常服務(99.9%、99.99%、99.999% 等等)。
可是在 Redis 語境中,高可用的含義彷佛要寬泛一些,除了保證提供正常服務(如主從分離、快速容災技術),還須要考慮數據容量的擴展、數據安全不會丟失等。
在 Redis 中,實現高可用的技術主要包括持久化、複製、哨兵和集羣,下面分別說明它們的做用,以及解決了什麼樣的問題:
持久化:持久化是最簡單的高可用方法(有時甚至不被歸爲高可用的手段),主要做用是數據備份,即將數據存儲在硬盤,保證數據不會因進程退出而丟失。
複製:複製是高可用 Redis 的基礎,哨兵和集羣都是在複製基礎上實現高可用的。複製主要實現了數據的多機備份,以及對於讀操做的負載均衡和簡單的故障恢復。
缺陷:故障恢復沒法自動化,寫操做沒法負載均衡,存儲能力受到單機的限制。
哨兵:在複製的基礎上,哨兵實現了自動化的故障恢復。
缺陷:寫操做沒法負載均衡;存儲能力受到單機的限制。
集羣:經過集羣,Redis 解決了寫操做沒法負載均衡,以及存儲能力受到單機限制的問題,實現了較爲完善的高可用方案。
Redis 持久化概述
持久化的功能:Redis 是內存數據庫,數據都是存儲在內存中。
爲了不進程退出致使數據的永久丟失,須要按期將 Redis 中的數據以某種形式(數據或命令)從內存保存到硬盤;當下次 Redis 重啓時,利用持久化文件實現數據恢復。
除此以外,爲了進行災難備份,能夠將持久化文件拷貝到一個遠程位置。
Redis 持久化分爲 RDB 持久化和 AOF 持久化:
前者將當前數據保存到硬盤
後者則是將每次執行的寫命令保存到硬盤(相似於 MySQL 的 binlog)
因爲 AOF 持久化的實時性更好,即當進程意外退出時丟失的數據更少,所以 AOF 是目前主流的持久化方式,不過 RDB 持久化仍然有其用武之地。
下面依次介紹 RDB 持久化和 AOF 持久化;因爲 Redis 各個版本之間存在差別,如無特殊說明,以 Redis 3.0 爲準。
RDB 持久化
RDB 持久化是將當前進程中的數據生成快照保存到硬盤(所以也稱做快照持久化),保存的文件後綴是 RDB;當 Redis 從新啓動時,能夠讀取快照文件恢復數據。
觸發條件
RDB 持久化的觸發分爲手動觸發和自動觸發兩種:
手動觸發
自動觸發
save 命令會阻塞 Redis 服務器進程,直到 RDB 文件建立完畢爲止,在 Redis 服務器阻塞期間,服務器不能處理任何命令請求。
而 bgsave 命令會建立一個子進程,由子進程來負責建立 RDB 文件,父進程(即 Redis 主進程)則繼續處理請求。
此時服務器執行日誌以下:
bgsave 命令執行過程當中,只有 fork 子進程時會阻塞服務器,而對於 save 命令,整個過程都會阻塞服務器。
所以 save 已基本被廢棄,線上環境要杜絕 save 的使用;後文中也將只介紹 bgsave 命令。
此外,在自動觸發 RDB 持久化時,Redis 也會選擇 bgsave 而不是 save 來進行持久化;下面介紹自動觸發 RDB 持久化的條件。
自動觸發:最多見的狀況是在配置文件中經過 save m n,指定當 m 秒內發生 n 次變化時,會觸發 bgsave。
例如,查看 Redis 的默認配置文件(Linux 下爲 Redis 根目錄下的 redis.conf),能夠看到以下配置信息:
其中 save 900 1 的含義是:當時間到 900 秒時,若是 Redis 數據發生了至少 1 次變化,則執行 bgsave。
save 300 10 和 save 60 10000 同理,當三個 save 條件知足任意一個時,都會引發 bgsave 的調用。
save m n 的實現原理:Redis 的 save m n,是經過 serverCron 函數、dirty 計數器和 lastsave 時間戳來實現的。
serverCron 是 Redis 服務器的週期性操做函數,默認每隔 100ms 執行一次;該函數對服務器的狀態進行維護,其中一項工做就是檢查 save m n 配置的條件是否知足,若是知足就執行 bgsave。
dirty 計數器是 Redis 服務器維持的一個狀態,記錄了上一次執行 bgsave/save 命令後,服務器狀態進行了多少次修改(包括增刪改);而當 save/bgsave 執行完成後,會將 dirty 從新置爲 0。
例如,若是 Redis 執行了 set mykey helloworld,則 dirty 值會 +1;若是執行了 sadd myset v1 v2 v3,則 dirty 值會 +3;注意 dirty 記錄的是服務器進行了多少次修改,而不是客戶端執行了多少修改數據的命令。
lastsave 時間戳也是 Redis 服務器維持的一個狀態,記錄的是上一次成功執行 save/bgsave 的時間。
save m n 的原理以下:每隔 100ms,執行 serverCron 函數;在 serverCron 函數中,遍歷 save m n 配置的保存條件,只要有一個條件知足,就進行 bgsave。
對於每個 save m n 條件,只有下面兩條同時知足時纔算知足:
當前時間-lastsave > m
dirty >= n
save m n 執行日誌:下圖是 save m n 觸發 bgsave 執行時,服務器打印日誌的狀況。
除了 save m n 之外,還有一些其餘狀況會觸發 bgsave:
在主從複製場景下,若是從節點執行全量複製操做,則主節點會執行 bgsave 命令,並將 RDB 文件發送給從節點。
執行 shutdown 命令時,自動執行 RDB 持久化,以下圖所示:
執行流程
前面介紹了觸發 bgsave 的條件,下面將說明 bgsave 命令的執行流程,以下圖所示:
圖片中的 5 個步驟所進行的操做以下:
Redis 父進程首先判斷:當前是否在執行 save 或 bgsave/bgrewriteaof(後面會詳細介紹該命令)的子進程,若是在執行則 bgsave 命令直接返回。
bgsave/bgrewriteaof 的子進程不能同時執行,主要是基於性能方面的考慮:兩個併發的子進程同時執行大量的磁盤寫操做,可能引發嚴重的性能問題。
父進程執行 fork 操做建立子進程,這個過程當中父進程是阻塞的,Redis 不能執行來自客戶端的任何命令。
父進程 fork 後,bgsave 命令返回」Background saving started」信息並再也不阻塞父進程,並能夠響應其餘命令。
子進程建立 RDB 文件,根據父進程內存快照生成臨時快照文件,完成後對原有文件進行原子替換。
子進程發送信號給父進程表示完成,父進程更新統計信息。
RDB 文件
RDB 文件是通過壓縮的二進制文件,下面介紹關於 RDB 文件的一些細節。
存儲路徑
RDB 文件的存儲路徑既能夠在啓動前配置,也能夠經過命令動態設定。
配置:dir 配置指定目錄,dbfilename 指定文件名。默認是 Redis 根目錄下的 dump.rdb 文件。
動態設定:Redis 啓動後也能夠動態修改 RDB 存儲路徑,在磁盤損害或空間不足時很是有用;執行命令爲 config set dir {newdir}和 config set dbfilename {newFileName}。
以下所示(Windows 環境):
RDB 文件格式
RDB 文件格式以下圖所示:
其中各個字段的含義說明以下:
REDIS:常量,保存着「REDIS」5 個字符。
db_version:RDB 文件的版本號,注意不是 Redis 的版本號。
SELECTDB 0 pairs:表示一個完整的數據庫(0 號數據庫),同理 SELECTDB 3 pairs 表示完整的 3 號數據庫。
只有當數據庫中有鍵值對時,RDB 文件中才會有該數據庫的信息(上圖所示的 Redis 中只有 0 號和 3 號數據庫有鍵值對);若是 Redis 中全部的數據庫都沒有鍵值對,則這一部分直接省略。
其中:SELECTDB 是一個常量,表明後面跟着的是數據庫號碼;0 和 3 是數據庫號碼;pairs 則存儲了具體的鍵值對信息,包括 key、value 值,及其數據類型、內部編碼、過時時間、壓縮信息等等。
EOF:常量,標誌 RDB 文件正文內容結束。
check_sum:前面全部內容的校驗和;Redis 在載入 RBD 文件時,會計算前面的校驗和並與 check_sum 值比較,判斷文件是否損壞。
壓縮
Redis 默認採用 LZF 算法對 RDB 文件進行壓縮。雖然壓縮耗時,可是能夠大大減少 RDB 文件的體積,所以壓縮默認開啓;能夠經過命令關閉:
須要注意的是,RDB 文件的壓縮並非針對整個文件進行的,而是對數據庫中的字符串進行的,且只有在字符串達到必定長度(20 字節)時纔會進行。
啓動時加載
RDB 文件的載入工做是在服務器啓動時自動執行的,並無專門的命令。可是因爲 AOF 的優先級更高,所以當 AOF 開啓時,Redis 會優先載入 AOF 文件來恢復數據。
只有當 AOF 關閉時,纔會在 Redis 服務器啓動時檢測 RDB 文件,並自動載入。服務器載入 RDB 文件期間處於阻塞狀態,直到載入完成爲止。
Redis 啓動日誌中能夠看到自動載入的執行:
Redis 載入 RDB 文件時,會對 RDB 文件進行校驗,若是文件損壞,則日誌中會打印錯誤,Redis 啓動失敗。
RDB 經常使用配置總結
下面是 RDB 經常使用的配置項,以及默認值,前面介紹過的這裏再也不詳細介紹:
save m n:bgsave 自動觸發的條件;若是沒有 save m n 配置,至關於自動的 RDB 持久化關閉,不過此時仍能夠經過其餘方式觸發。
stop-writes-on-bgsave-error yes:當 bgsave 出現錯誤時,Redis 是否中止執行寫命令;設置爲 yes,則當硬盤出現問題時,能夠及時發現,避免數據的大量丟失。
設置爲 no,則 Redis 無視 bgsave 的錯誤繼續執行寫命令,當對 Redis 服務器的系統(尤爲是硬盤)使用了監控時,該選項考慮設置爲 no。
rdbcompression yes:是否開啓 RDB 文件壓縮。
rdbchecksum yes:是否開啓 RDB 文件的校驗,在寫入文件和讀取文件時都起做用;關閉 checksum 在寫入文件和啓動文件時大約能帶來 10% 的性能提高,可是數據損壞時沒法發現。
dbfilename dump.rdb:RDB 文件名。
dir ./:RDB 文件和 AOF 文件所在目錄。
AOF 持久化
RDB 持久化是將進程數據寫入文件,而 AOF 持久化(即 Append Only File 持久化),則是將 Redis 執行的每次寫命令記錄到單獨的日誌文件中(有點像 MySQL 的 binlog),當 Redis 重啓時再次執行 AOF 文件中的命令來恢復數據。
與 RDB 相比,AOF 的實時性更好,所以已成爲主流的持久化方案。
開啓 AOF
Redis 服務器默認開啓 RDB,關閉 AOF;要開啓 AOF,須要在配置文件中配置:appendonly yes。
執行流程
因爲須要記錄 Redis 的每條寫命令,所以 AOF 不須要觸發,下面介紹 AOF 的執行流程。
AOF 的執行流程包括:
命令追加(append):將 Redis 的寫命令追加到緩衝區 aof_buf。
文件寫入(write)和文件同步(sync):根據不一樣的同步策略將 aof_buf 中的內容同步到硬盤。
文件重寫(rewrite):按期重寫 AOF 文件,達到壓縮的目的。
Redis 先將寫命令追加到緩衝區,而不是直接寫入文件,主要是爲了不每次有寫命令都直接寫入硬盤,致使硬盤 IO 成爲 Redis 負載的瓶頸。
命令追加的格式是 Redis 命令請求的協議格式,它是一種純文本格式,具備兼容性好、可讀性強、容易處理、操做簡單避免二次開銷等優勢,具體格式略。
在 AOF 文件中,除了用於指定數據庫的 select 命令(如 select 0 爲選中 0 號數據庫)是由 Redis 添加的,其餘都是客戶端發送來的寫命令。
Redis 提供了多種 AOF 緩存區的同步文件策略,策略涉及到操做系統的 write 函數和 fsync 函數,說明以下:
爲了提升文件寫入效率,在現代操做系統中,當用戶調用 write 函數將數據寫入文件時,操做系統一般會將數據暫存到一個內存緩衝區裏,當緩衝區被填滿或超過了指定時限後,才真正將緩衝區的數據寫入到硬盤裏。
這樣的操做雖然提升了效率,但也帶來了安全問題:若是計算機停機,內存緩衝區中的數據會丟失。
所以系統同時提供了 fsync、fdatasync 等同步函數,能夠強制操做系統馬上將緩衝區中的數據寫入到硬盤裏,從而確保數據的安全性。
AOF 緩存區的同步文件策略由參數 appendfsync 控制,各個值的含義以下:
always:命令寫入 aof_buf 後當即調用系統 fsync 操做同步到 AOF 文件,fsync 完成後線程返回。
這種狀況下,每次有寫命令都要同步到 AOF 文件,硬盤 IO 成爲性能瓶頸,Redis 只能支持大約幾百 TPS 寫入,嚴重下降了 Redis 的性能。
即使是使用固態硬盤(SSD),每秒大約也只能處理幾萬個命令,並且會大大下降 SSD 的壽命。
no:命令寫入 aof_buf 後調用系統 write 操做,不對 AOF 文件作 fsync 同步;同步由操做系統負責,一般同步週期爲 30 秒。
這種狀況下,文件同步的時間不可控,且緩衝區中堆積的數據會不少,數據安全性沒法保證。
everysec:命令寫入 aof_buf 後調用系統 write 操做,write 完成後線程返回;fsync 同步文件操做由專門的線程每秒調用一次。
everysec 是前述兩種策略的折中,是性能和數據安全性的平衡,所以是 Redis 的默認配置,也是咱們推薦的配置。
隨着時間流逝,Redis 服務器執行的寫命令愈來愈多,AOF 文件也會愈來愈大;過大的 AOF 文件不只會影響服務器的正常運行,也會致使數據恢復須要的時間過長。
文件重寫是指按期重寫 AOF 文件,減少 AOF 文件的體積。須要注意的是,AOF 重寫是把 Redis 進程內的數據轉化爲寫命令,同步到新的 AOF 文件;不會對舊的 AOF 文件進行任何讀取、寫入操做!
關於文件重寫須要注意的另外一點是:對於 AOF 持久化來講,文件重寫雖然是強烈推薦的,但並非必須的。即便沒有文件重寫,數據也能夠被持久化並在 Redis 啓動的時候導入。
所以在一些實現中,會關閉自動的文件重寫,而後經過定時任務在天天的某一時刻定時執行。
文件重寫之因此可以壓縮 AOF 文件,緣由在於:
過時的數據再也不寫入文件。
無效的命令再也不寫入文件:若有些數據被重複設值(set mykey v1,set mykey v2)、有些數據被刪除了(sadd myset v1,del myset)等等。
多條命令能夠合併爲一個:如 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。
經過上述內容能夠看出,因爲重寫後 AOF 執行的命令減小了,文件重寫既能夠減小文件佔用的空間,也能夠加快恢復速度。
文件重寫的觸發
文件重寫的觸發,分爲手動觸發和自動觸發:
手動觸發,直接調用 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 操做。
自動觸發 bgrewriteaof 時,能夠看到服務器日誌以下:
文件重寫的流程
文件重寫流程以下圖所示:
關於文件重寫的流程,有兩點須要特別注意:
重寫由父進程 fork 子進程進行。
重寫期間 Redis 執行的寫命令,須要追加到新的 AOF 文件中,爲此 Redis 引入了 aof_rewrite_buf 緩存。
對照上圖,文件重寫的流程以下:
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 重寫。
啓動時加載
前面提到過,當 AOF 開啓時,Redis 啓動時會優先載入 AOF 文件來恢復數據;只有當 AOF 關閉時,纔會載入 RDB 文件恢復數據。
當 AOF 開啓,且 AOF 文件存在時,Redis 啓動日誌:
當 AOF 開啓,但 AOF 文件不存在時,即便 RDB 文件存在也不會加載(更早的一些版本可能會加載,但 3.0 不會),Redis 啓動日誌以下:
文件校驗
與載入 RDB 文件相似,Redis 載入 AOF 文件時,會對 AOF 文件進行校驗,若是文件損壞,則日誌中會打印錯誤,Redis 啓動失敗。
但若是是 AOF 文件結尾不完整(機器忽然宕機等容易致使文件尾部不完整),且 aof-load-truncated 參數開啓,則日誌中會輸出警告,Redis 忽略掉 AOF 文件的尾部,啓動成功。
aof-load-truncated 參數默認是開啓的:
僞客戶端
由於 Redis 的命令只能在客戶端上下文中執行,而載入 AOF 文件時命令是直接從文件中讀取的,並非由客戶端發送。
所以 Redis 服務器在載入 AOF 文件以前,會建立一個沒有網絡鏈接的客戶端,以後用它來執行 AOF 文件中的命令,命令執行的效果與帶網絡鏈接的客戶端徹底同樣。
AOF 經常使用配置總結
下面是 AOF 經常使用的配置項,以及默認值:
appendonly no:是否開啓 AOF。
appendfilename "appendonly.aof":AOF 文件名。
dir ./:RDB 文件和 AOF 文件所在目錄。
appendfsync everysec:fsync 持久化策略。
no-appendfsync-on-rewrite no:AOF 重寫期間是否禁止 fsync;若是開啓該選項,能夠減輕文件重寫時 CPU 和硬盤的負載(尤爲是硬盤),可是可能會丟失 AOF 重寫期間的數據;須要在負載和安全性之間進行平衡。
auto-aof-rewrite-percentage 100:文件重寫觸發條件之一。
auto-aof-rewrite-min-size 64mb:文件重寫觸發提交之一。
aof-load-truncated yes:若是 AOF 文件結尾損壞,Redis 啓動時是否仍載入 AOF 文件。
方案選擇與常見問題
前面介紹了 RDB 和 AOF 兩種持久化方案的細節,下面介紹 RDB 和 AOF 的特色、如何選擇持久化方案,以及在持久化過程當中常遇到的問題等。
RDB 和 AOF 的優缺點
RDB 和 AOF 各有優缺點:
RDB 持久化
優勢:RDB 文件緊湊,體積小,網絡傳輸快,適合全量複製;恢復速度比 AOF 快不少。固然,與 AOF 相比,RDB 最重要的優勢之一是對性能的影響相對較小。
缺點:RDB 文件的致命缺點在於其數據快照的持久化方式決定了必然作不到實時持久化,而在數據愈來愈重要的今天,數據的大量丟失不少時候是沒法接受的,所以 AOF 持久化成爲主流。
此外,RDB 文件須要知足特定格式,兼容性差(如老版本的 Redis 不兼容新版本的 RDB 文件)。
AOF 持久化
與 RDB 持久化相對應,AOF 的優勢在於支持秒級持久化、兼容性好,缺點是文件大、恢復速度慢、對性能影響大。
持久化策略選擇
在介紹持久化策略以前,首先要明白不管是 RDB 仍是 AOF,持久化的開啓都是要付出性能方面代價的:
對於 RDB 持久化,一方面是 bgsave 在進行 fork 操做時 Redis 主進程會阻塞,另外一方面,子進程向硬盤寫數據也會帶來 IO 壓力。
對於 AOF 持久化,向硬盤寫數據的頻率大大提升(everysec 策略下爲秒級),IO 壓力更大,甚至可能形成 AOF 追加阻塞問題(後面會詳細介紹這種阻塞)。
此外,AOF 文件的重寫與 RDB 的 bgsave 相似,會有 fork 時的阻塞和子進程的 IO 壓力問題。
相對來講,因爲 AOF 向硬盤中寫數據的頻率更高,所以對 Redis 主進程性能的影響會更大。
在實際生產環境中,根據數據量、應用對數據的安全要求、預算限制等不一樣狀況,會有各類各樣的持久化策略。
如徹底不使用任何持久化、使用 RDB 或 AOF 的一種,或同時開啓 RDB 和 AOF 持久化等。
此外,持久化的選擇必須與 Redis 的主從策略一塊兒考慮,由於主從複製與持久化一樣具備數據備份的功能,並且主機 master 和從機 slave 能夠獨立的選擇持久化方案。
下面分場景來討論持久化策略的選擇,討論也只是做爲參考,實際方案可能更復雜更具多樣性:
若是 Redis 中的數據徹底丟棄也沒有關係(如 Redis 徹底用做 DB 層數據的 Cache),那麼不管是單機,仍是主從架構,均可以不進行任何持久化。
在單機環境下(對於我的開發者,這種狀況可能比較常見),若是能夠接受十幾分鍾或更多的數據丟失,選擇 RDB 對 Redis 的性能更加有利;若是隻能接受秒級別的數據丟失,應該選擇 AOF。
但在多數狀況下,咱們都會配置主從環境,slave 的存在既能夠實現數據的熱備,也能夠進行讀寫分離分擔 Redis 讀請求,以及在 master 宕掉後繼續提供服務。
在這種狀況下,一種可行的作法是:
master:徹底關閉持久化(包括 RDB 和 AOF),這樣可讓 master 的性能達到最好。
slave:關閉 RDB,開啓 AOF(若是對數據安全要求不高,開啓 RDB 關閉 AOF 也能夠),並定時對持久化文件進行備份(如備份到其餘文件夾,並標記好備份的時間)。
而後關閉 AOF 的自動重寫,而後添加定時任務,在天天 Redis 閒時(如凌晨 12 點)調用 bgrewriteaof。
這裏須要解釋一下,爲何開啓了主從複製,能夠實現數據的熱備份,還須要設置持久化呢?
由於在一些特殊狀況下,主從複製仍然不足以保證數據的安全,例如:
master 和 slave 進程同時中止:考慮這樣一種場景,若是 master 和 slave 在同一棟大樓或同一個機房,則一次停電事故就可能致使 master 和 slave 機器同時關機,Redis 進程中止;若是沒有持久化,則面臨的是數據的徹底丟失。
master誤重啓:考慮這樣一種場景,master 服務由於故障宕掉了,若是系統中有自動拉起機制(即檢測到服務中止後重啓該服務)將 master 自動重啓,因爲沒有持久化文件,那麼 master 重啓後數據是空的,slave 同步數據也變成了空的;若是 master 和 slave 都沒有持久化,一樣會面臨數據的徹底丟失。
須要注意的是,即使是使用了哨兵(關於哨兵後面會有文章介紹)進行自動的主從切換,也有可能在哨兵輪詢到 master 以前,便被自動拉起機制重啓了。所以,應儘可能避免「自動拉起機制」和「不作持久化」同時出現。
異地災備:上述討論的幾種持久化策略,針對的都是通常的系統故障,如進程異常退出、宕機、斷電等,這些故障不會損壞硬盤。
可是對於一些可能致使硬盤損壞的災難狀況,如火災地震,就須要進行異地災備。
例如對於單機的情形,能夠定時將 RDB 文件或重寫後的 AOF 文件,經過 scp 拷貝到遠程機器,如阿里雲、AWS 等。
對於主從的情形,能夠定時在 master 上執行 bgsave,而後將 RDB 文件拷貝到遠程機器,或者在 slave 上執行 bgrewriteaof 重寫 AOF 文件後,將 AOF 文件拷貝到遠程機器上。
通常來講,因爲 RDB 文件文件小、恢復快,所以災難恢復經常使用 RDB 文件;異地備份的頻率根據數據安全性的須要及其餘條件來肯定,但最好不要低於一天一次。
fork 阻塞:CPU 的阻塞
在 Redis 的實踐中,衆多因素限制了 Redis 單機的內存不能過大,例如:
當面對請求的暴增,須要從庫擴容時,Redis 內存過大會致使擴容時間太長。
當主機宕機時,切換主機後須要掛載從庫,Redis 內存過大致使掛載速度過慢。
持久化過程當中的 fork 操做。
首先說明一下 fork 操做:父進程經過 fork 操做能夠建立子進程;子進程建立後,父子進程共享代碼段,不共享進程的數據空間,可是子進程會得到父進程的數據空間的副本。
在操做系統 fork 的實際實現中,基本都採用了寫時複製技術,即在父/子進程試圖修改數據空間以前,父子進程實際上共享數據空間。
可是當父/子進程的任何一個試圖修改數據空間時,操做系統會爲修改的那一部分(內存的一頁)製做一個副本。
雖然 fork 時,子進程不會複製父進程的數據空間,可是會複製內存頁表(頁表至關於內存的索引、目錄);父進程的數據空間越大,內存頁表越大,fork 時複製耗時也會越多。
在 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 中,若是 AOF 緩衝區的文件同步策略爲 everysec,則在主線程中,命令寫入 aof_buf 後調用系統 write 操做,write 完成後主線程返回。
fsync 同步文件操做由專門的文件同步線程每秒調用一次。這種作法的問題在於,若是硬盤負載太高,那麼 fsync 操做可能會超過 1s。
若是 Redis 主線程持續高速向 aof_buf 寫入命令,硬盤的負載可能會愈來愈大,IO 資源消耗更快;若是此時 Redis 進程異常退出,丟失的數據也會愈來愈多,可能遠超過 1s。
爲此,Redis 的處理策略是這樣的:主線程每次進行 AOF 會對比上次 fsync 成功的時間;若是距上次不到 2s,主線程直接返回;若是超過 2s,則主線程阻塞直到 fsync 同步完成。
所以,若是系統硬盤負載過大致使 fsync 速度太慢,會致使 Redis 主線程的阻塞;此外,使用 everysec 配置,AOF 最多可能丟失 2s 的數據,而不是 1s。
AOF 追加阻塞問題定位的方法:
監控 info Persistence 中的 aof_delayed_fsync:當 AOF 追加阻塞發生時(即主線程等待 fsync 而阻塞),該指標累加。
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.
若是 AOF 追加阻塞頻繁發生,說明系統的硬盤負載太大,能夠考慮更換 IO 速度更快的硬盤,或者經過 IO 監控分析工具對系統的 IO 負載進行分析,如 iostat(系統級 io)、iotop(io 版的 top)、pidstat 等。
info 命令與持久化
前面提到了一些經過 info 命令查看持久化相關狀態的方法,下面來總結一下。
info Persistence
執行結果以下:
其中比較重要的包括:
rdb_last_bgsave_status:上次 bgsave 執行結果,能夠用於發現 bgsave 錯誤。
rdb_last_bgsave_time_sec:上次 bgsave 執行時間(單位是 s),能夠用於發現 bgsave 是否耗時過長。
aof_enabled:AOF 是否開啓。
aof_last_rewrite_time_sec:上次文件重寫執行時間(單位是 s),能夠用於發現文件重寫是否耗時過長。
aof_last_bgrewrite_status:上次 bgrewrite 執行結果,能夠用於發現 bgrewrite 錯誤。
aof_buffer_length 和 aof_rewrite_buffer_length:AOF 緩存區大小和 AOF 重寫緩衝區大小。
aof_delayed_fsync:AOF 追加阻塞狀況的統計。
info stats
其中與持久化關係較大的是:latest_fork_usec,表明上次 fork 耗時,能夠參見前面的討論。
總結
本文主要內容能夠總結以下:
持久化在 Redis 高可用中的做用:數據備份,與主從複製相比強調的是由內存到硬盤的備份。
RDB 持久化:將數據快照備份到硬盤;介紹了其觸發條件(包括手動出發和自動觸發)、執行流程、RDB 文件等,特別須要注意的是文件保存操做由 fork 出的子進程來進行。
AOF 持久化:將執行的寫命令備份到硬盤(相似於 MySQL 的 binlog),介紹了其開啓方法、執行流程等,特別須要注意的是文件同步策略的選擇(everysec)、文件重寫的流程。
一些現實的問題:包括如何選擇持久化策略,以及須要注意的 fork 阻塞、AOF 追加阻塞等。
●編號352,輸入編號直達本文
●輸入m獲取文章目錄
推薦↓↓↓Web開發
更多推薦《18個技術類公衆微信》
涵蓋:程序人生、算法與數據結構、黑客技術與網絡安全、大數據技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、數據庫、運維等。