redis持久化,主從及數據備份

轉自:http://blog.csdn.net/lang_man_xing/article/details/38386113


現在在項目裏已經大量使用redis了,爲了提高redis的性能和可靠性我們需要知道和做到以下幾件事:

常用內存優化手段與參數

redis的性能如何是完全依賴於內存的,所以我們需要知道如何來控制和節省內存。

首先最重要的一點是不要開啓Redis的VM選項,即虛擬內存功能,這個本來是作爲Redis存儲超出物理內存數據的一種數據在內存與磁盤換入換出的一個持久化策略,但是其內存管理成本非常的高,所以要關閉VM功能,請檢查你的redis.conf文件中 vm-enabled 爲 no。

其次最好設置下redis.conf中的maxmemory選項,該選項是告訴Redis當使用了多少物理內存後就開始拒絕後續的寫入請求,該參數能很好的保護好你的Redis不會因爲使用了過多的物理內存而導致swap,最終嚴重影響性能甚至崩潰。

另外Redis爲不同數據類型分別提供了一組參數來控制內存使用,我們知道Redis Hash是value內部爲一個HashMap,如果該Map的成員數比較少,則會採用類似一維線性的緊湊格式來存儲該Map, 即省去了大量指針的內存開銷,這個參數控制對應在redis.conf配置文件中下面2項:

hash-max-zipmap-entries 64 
hash-max-zipmap-value 512 

含義是當value這個Map內部不超過多少個成員時會採用線性緊湊格式存儲,默認是64,即value內部有64個以下的成員就是使用線性緊湊存儲,超過該值自動轉成真正的HashMap。

hash-max-zipmap-value 含義是當 value這個Map內部的每個成員值長度不超過多少字節就會採用線性緊湊存儲來節省空間。

以上2個條件任意一個條件超過設置值都會轉換成真正的HashMap,也就不會再節省內存了,那麼這個值是不是設置的越大越好呢,答案當然是否定的,HashMap的優勢就是查找和操作的時間複雜度都是O(1)的,而放棄Hash採用一維存儲則是O(n)的時間複雜度,如果成員數量很少,則影響不大,否則會嚴重影響性能,所以要權衡好這個值的設置,總體上還是最根本的時間成本和空間成本上的權衡。

同樣類似的參數還有:

list-max-ziplist-entries 512

說明:list數據類型多少節點以下會採用去指針的緊湊存儲格式。

list-max-ziplist-value 64 

說明:list數據類型節點值大小小於多少字節會採用緊湊存儲格式。

set-max-intset-entries 512 

說明:set數據類型內部數據如果全部是數值型,且包含多少節點以下會採用緊湊格式存儲。

Redis內部實現沒有對內存分配方面做過多的優化,在一定程度上會存在內存碎片,不過大多數情況下這個不會成爲Redis的性能瓶頸,不過如果在Redis內部存儲的大部分數據是數值型的話,Redis內部採用了一個shared integer的方式來省去分配內存的開銷,即在系統啓動時先分配一個從1~n 那麼多個數值對象放在一個池子中,如果存儲的數據恰好是這個數值範圍內的數據,則直接從池子裏取出該對象,並且通過引用計數的方式來共享,這樣在系統存儲了大量數值下,也能一定程度上節省內存並且提高性能,這個參數值n的設置需要修改源代碼中的一行宏定義REDIS_SHARED_INTEGERS,該值默認是10000,可以根據自己的需要進行修改,修改後重新編譯就可以了。

持久化

redis是一個支持持久化的內存數據庫,也就是說redis需要經常將內存中的數據同步到磁盤來保證持久化。redis支持兩種持久化方式,一種是 Snapshotting(快照)也是默認方式,另一種是Append-only file(縮寫aof)的方式。

snapshotting

快照是默認的持久化方式。這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲dump.rdb。可以通過配置設置自動做快照持久化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置:

[plain] view plaincopy
  1. save 900 1 #900秒內如果超過1個key被修改,則發起快照保存  
  2. save 300 10 #300秒內容如超過10個key被修改,則發起快照保存  
  3. save 60 10000 #60秒內容如超過10000個key被修改,則發起快照保存  
也可以命令行的方式讓redis進行snapshotting:
[plain] view plaincopy
  1. redis-cli -h ip -p port bgsave  
保存快照有save和bgsave兩個命令,save操作是在主線程中保存快照的,由於redis是用一個主線程來處理所有 client的請求,這種方式會阻塞所有client請求,所以不推薦使用。

快照生成過程大致如下:

  1. redis調用fork,現在有了子進程和父進程;
  2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由於os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數據是fork時刻整個數據庫的一個快照;
  3. 當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,然後子進程退出。

同時snapshotting也有不足的,因爲兩次快照操作之間是有時間間隔的,一旦數據庫出現問題,那麼快照文件中保存的數據並不是全新的,從上次快照文件生成到Redis停機這段時間的數據全部丟掉了。如果業務對數據準確性要求極高的話,就得采用aof持久化機制了。

aof

aof 比快照方式有更好的持久化性,是由於在使用aof持久化方式時,redis會將每一個收到的寫命令都通過write函數追加到文件中(默認是 appendonly.aof)。當redis重啓時會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。當然由於os會在內核中緩存 write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會丟失部分修改。不過我們可以通過配置文件告訴redis我們想要通過fsync函數強制os寫入到磁盤的時機。有三種方式如下(默認是:每秒fsync一次):

[plain] view plaincopy
  1. appendonly yes //啓用aof持久化方式  
  2. # appendfsync always //每次收到寫命令就立即強制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用  
  3. appendfsync everysec //每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦  
  4. # appendfsync no //完全依賴os,性能最好,持久化沒保證  

aof 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。例如我們調用incr test命令100次,文件中必須保存全部的100條命令,其實有99條都是多餘的。因爲要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。爲了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內存中的數據 以命令的方式保存到臨時文件中,最後替換原來的文件。bgrewriteaof命令如下:

[plain] view plaincopy
  1. redis-cli -h ip -p port bgrewriteaof  
bgrewriteaof命令執行過程如下:
  1. redis調用fork ,現在有父子兩個進程;
  2. 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令;
  3. 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證如果子進程重寫失敗的話並不會出問題;
  4. 當子進程把快照內容寫入以命令方式寫到臨時文件中後,子進程發信號通知父進程。然後父進程把緩存的寫命令也寫入到臨時文件;
  5. 現在父進程可以使用臨時文件替換老的aof文件,並重命名,後面收到的寫命令也開始往新的aof文件中追加。

這兩種持久化方式有各自的特點,快照相對性能影響不大,但一旦崩潰,數據量丟失較大,而aof數據安全性較高,但性能影響較大,這就得根據業務特點自行選擇了。

主從複製

redis的主從複製策略是通過其持久化的rdb文件來實現的,其過程是先dump出rdb文件,將rdb文件全量傳輸給slave,然後再將dump後的操作實時同步到slave中。

要使用主從功能需要在slave端進行簡單的配置:
[plain] view plaincopy
  1. slaveof master_ip master_port #如果這臺機器是臺redis slave,可以打開這個設置。  
  2. slave-serve-stale-data no #如果slave 無法與master 同步,設置成slave不可讀,方便監控腳本發現問題。  

配置好之後啓動slave端就可以進行主從複製了,主從複製的過程大致如下:

  1. Slave端在配置文件中添加了slaveof指令,於是Slave啓動時讀取配置文件,初始狀態爲REDIS_REPL_CONNECT;
  2. Slave端在定時任務serverCron(Redis內部的定時器觸發事件)中連接Master,發送sync命令,然後阻塞等待master發送回其內存快照文件(最新版的Redis已經不需要讓Slave阻塞);
  3. Master端收到sync命令簡單判斷是否有正在進行的內存快照子進程,沒有則立即開始內存快照,有則等待其結束,當快照完成後會將該文件發送給Slave端;
  4. Slave端接收Master發來的內存快照文件,保存到本地,待接收完成後,清空內存表,重新讀取Master發來的內存快照文件,重建整個內存表數據結構,並最終狀態置位爲 REDIS_REPL_CONNECTED狀態,Slave狀態機流轉完成;
  5. Master端在發送快照文件過程中,接收的任何會改變數據集的命令都會暫時先保存在Slave網絡連接的發送緩存隊列裏(list數據結構),待快照完成後,依次發給Slave,之後收到的命令相同處理,並將狀態置位爲 REDIS_REPL_ONLINE。
整個複製過程完成,流程如下圖所示:

從以上的複製過程中可以發現,Slave從庫在連接Master主庫時,Master會進行內存快照,然後把整個快照文件發給Slave,也就是沒有象MySQL那樣有複製位置的概念,即無增量複製,如果一個master連接多個slave,就會比較影響master性能了。

數據備份策略

具體的備份策略是可以很靈活的,比如可以大致如下:

  1. 爲了提高master的性能關閉master的持久化機制,即不進行快照也不進行aof,而是在凌晨訪問量低的時候定時的用bgsave命令進行快照,並將快照文件保存到備份服務器上;
  2. slave端開啓aof機制,並定時的用bgrewriteaof 進行數據壓縮,將壓縮後的數據文件保存到備份服務器上;
  3. 定時的檢查master與slave上的數據是否一致;
  4. 當master出問題並需要恢復時,如果採用master的備份快照恢復直接將備份的dump.rdb拷貝到相應路徑下重啓即可;如果要從slave端恢復,需要在slave端執行一次快照,然後將快照文件拷貝到master路徑下然後重啓即可。不過有一點需要注意的是,master重啓時slave端數據會被沖掉,所以slave端要在master重啓前做好備份。

持久化磁盤IO方式及其帶來的問題

有Redis線上運維經驗的人會發現Redis在物理內存使用比較多,但還沒有超過實際物理內存總容量時就會發生不穩定甚至崩潰的問題,有人認爲是基於快照方式持久化的fork系統調用造成內存佔用加倍而導致的,這種觀點是不準確的,因爲fork 調用的copy-on-write機制是基於操作系統頁這個單位的,也就是隻有有寫入的髒頁會被複制,但是一般的系統不會在短時間內所有的頁都發生了寫入而導致複製,那麼是什麼原因導致Redis崩潰的呢?

答案是Redis的持久化使用了Buffer IO造成的,所謂Buffer IO是指Redis對持久化文件的寫入和讀取操作都會使用物理內存的Page Cache,而大多數數據庫系統會使用Direct IO來繞過這層Page Cache並自行維護一個數據的Cache,而當Redis的持久化文件過大(尤其是快照文件),並對其進行讀寫時,磁盤文件中的數據都會被加載到物理內存中作爲操作系統對該文件的一層Cache,而這層Cache的數據與Redis內存中管理的數據實際是重複存儲的,雖然內核在物理內存緊張時會做Page Cache的剔除工作,但內核可能認爲某塊Page Cache更重要,而讓你的進程開始Swap,這時你的系統就會開始出現不穩定或者崩潰了。經驗是當你的Redis物理內存使用超過內存總容量的3/5時就會開始比較危險了。



1、 快照的方式持久化到磁盤
自動持久化規則配置
save 900 1
save 300 10
save 60 10000
上面的配置規則意思如下:
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
redis也可以關閉自動持久化,註釋掉這些save配置,或者save “”
如果後臺保存到磁盤發生錯誤,將停止寫操作.
stop-writes-on-bgsave-error yes
使用LZF壓縮rdb文件,這會耗CPU, 但是可以減少磁盤佔用.
rdbcompression yes
保存rdb和加載rdb文件的時候檢驗,可以防止錯誤,但是要付出約10%的性能,可以關閉他,提高性能。
rdbchecksum yes
導出的rdb文件名
dbfilename dump.rdb
設置工作目錄, rdb文件會寫到該目錄, append only file也會存儲在該目錄下.
dir ./
Redis自動快照保存到磁盤或者調用bgsave,是後臺進程完成的,其他客戶端仍然和可以讀寫redis服務器,後臺保存快照到磁盤會佔用大量內存。調用save保存內存中的數據到磁盤,將阻塞客戶端請求,直到保存完畢。
調用shutdown命令,Redis服務器會先調用save,所有數據持久化到磁盤之後纔會真正退出。
對於數據丟失的問題:
如果服務器crash,從上一次快照之後的數據將全部丟失。所以在設置保存規則的時候,要根據實際業務設置允許的範圍。
如果對於數據敏感的業務,在程序中要使用恰當的日誌,在服務器crash之後,通過日誌恢復數據。
2、 Append-only file 的方式持久化
 
另外一種方式爲遞增的方式,將會引起數據變化的操作, 持久化到文件中, 重啓redis的時候,通過操作命令,恢復數據.
 
每次執行寫操作命令之後,都會將數據寫到server.aofbuf中。
# appendfsync always
appendfsync everysec
# appendfsync no
 
當配置爲always的時候,每次server.aofbuf中的數據寫入到文件之後,纔會返回給客戶端,這樣可以保證數據不丟,但是頻繁的IO操作,會降低性能。
 
everysec每秒寫一次,這可能會丟失一秒內的操作。
 
 aof最大的問題就是隨着時間append file會變的很大,所以我們需要bgrewriteaof命令重新整理文件,只保留最新的kv數據。
發佈了73 篇原創文章 · 獲贊 18 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章