redis持久化,主從及資料備份(2),redis主從資料備份
在在項目裡已經大量使用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
- save 900 1 #900秒內如果超過1個key被修改,則發起快照儲存
- save 300 10 #300秒內容如超過10個key被修改,則發起快照儲存
- save 60 10000 #60秒內容如超過10000個key被修改,則發起快照儲存
也可以命令列的方式讓redis進行snapshotting:
[plain] view plaincopy
- redis-cli -h ip -p port bgsave
儲存快照有save和bgsave兩個命令,save操作是在主線程中儲存快照的,由於redis是用一個主線程來處理所有 client的請求,這種方式會阻塞所有client請求,所以不推薦使用。
快照產生過程大致如下:
- redis調用fork,現在有了子進程和父進程;
- 父進程繼續處理client請求,子進程負責將記憶體內容寫入到臨時檔案。由於os的寫時複製機制(copy on write)父子進程會共用相同的物理頁面,當父進程處理寫請求時os會為父進程要修改的頁面棄置站台,而不是寫共用的頁面。所以子進程的地址空間內的資料是fork時刻整個資料庫的一個快照;
- 當子進程將快照寫入臨時檔案完畢後,用臨時檔案替換原來的快照檔案,然後子進程退出。
同時snapshotting也有不足的,因為兩次快照操作之間是有時間間隔的,一旦資料庫出現問題,那麼快照檔案中儲存的資料並不是全新的,從上次快照檔案產生到Redis停機這段時間的資料全部丟掉了。如果業務對資料準確性要求極高的話,就得採用aof持久化機制了。
aof
aof 比快照方式有更好的持久化性,是由於在使用aof持久化方式時,redis會將每一個收到的寫命令都通過write函數追加到檔案中(預設是 appendonly.aof)。當redis重啟時會通過重新執行檔案中儲存的寫命令來在記憶體中重建整個資料庫的內容。當然由於os會在核心中緩衝 write做的修改,所以可能不是立即寫到磁碟上。這樣aof方式的持久化也還是有可能會丟失部分修改。不過我們可以通過設定檔告訴redis我們想要通過fsync函數強制os寫入到磁碟的時機。有三種方式如下(預設是:每秒fsync一次):
[plain] view plaincopy
- appendonly yes //啟用aof持久化方式
- # appendfsync always //每次收到寫命令就立即強制寫入磁碟,最慢的,但是保證完全的持久化,不推薦使用
- appendfsync everysec //每秒鐘強制寫入磁碟一次,在效能和持久化方面做了很好的折中,推薦
- # appendfsync no //完全依賴os,效能最好,持久化沒保證
aof 的方式也同時帶來了另一個問題。持久化檔案會變的越來越大。例如我們調用incr test命令100次,檔案中必須儲存全部的100條命令,其實有99條都是多餘的。因為要恢複資料庫的狀態其實檔案中儲存一條set test 100就夠了。為了壓縮aof的持久化檔案。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將記憶體中的資料 以命令的方式儲存到臨時檔案中,最後替換原來的檔案。bgrewriteaof命令如下:
[plain] view plaincopy
- redis-cli -h ip -p port bgrewriteaof
bgrewriteaof命令執行過程如下:
- redis調用fork ,現在有父子兩個進程;
- 子進程根據記憶體中的資料庫快照集,往臨時檔案中寫入重建資料庫狀態的命令;
- 父進程繼續處理client請求,除了把寫命令寫入到原來的aof檔案中。同時把收到的寫命令緩衝起來。這樣就能保證如果子進程重寫失敗的話並不會出問題;
- 當子進程把快照內容寫入以命令方式寫到臨時檔案中後,子進程發訊號通知父進程。然後父進程把緩衝的寫命令也寫入到臨時檔案;
- 現在父進程可以使用臨時檔案替換老的aof檔案,並重新命名,後面收到的寫命令也開始往新的aof檔案中追加。
這兩種持久化方式有各自的特點,快照相對效能影響不大,但一旦崩潰,資料量丟失較大,而aof資料安全性較高,但效能影響較大,這就得根據業務特點自行選擇了。
主從複製
redis的主從複製策略是通過其持久化的rdb檔案來實現的,其過程是先dump出rdb檔案,將rdb檔案全量傳輸給slave,然後再將dump後的操作即時同步到slave中。
要使用主從功能需要在slave端進行簡單的配置:
[plain] view plaincopy
- slaveof master_ip master_port #如果這台機器是台redis slave,可以開啟這個設定。
- slave-serve-stale-data no #如果slave 無法與master 同步,設定成slave不可讀,方便監控指令碼發現問題。
配置好之後啟動slave端就可以進行主從複製了,主從複製的過程大致如下:
- Slave端在設定檔中添加了slaveof指令,於是Slave啟動時讀取設定檔,初始狀態為REDIS_REPL_CONNECT;
- Slave端在定時任務serverCron(Redis內部的定時器觸發事件)中串連Master,發送sync命令,然後阻塞等待master發送回其記憶體快照檔案(最新版的Redis已經不需要讓Slave阻塞);
- Master端收到sync命令簡單判斷是否有進行中的記憶體快照子進程,沒有則立即開始記憶體快照,有則等待其結束,當快照完成後會將該檔案發送給Slave端;
- Slave端接收Master發來的記憶體快照檔案,儲存到本地,待接收完成後,清空記憶體表,重新讀取Master發來的記憶體快照檔案,重建整個記憶體表資料結構,並最終狀態置位為 REDIS_REPL_CONNECTED狀態,Slave狀態機器流轉完成;
- Master端在發送快照檔案過程中,接收的任何會改變資料集的命令都會暫時先儲存在Slave網路連接的發送緩衝隊列裡(list資料結構),待快照完成後,依次發給Slave,之後收到的命令相同處理,並將狀態置位為 REDIS_REPL_ONLINE。
整個複製過程完成,流程如所示:
從以上的複製過程中可以發現,Slave從庫在串連Master主庫時,Master會進行記憶體快照,然後把整個快照檔案發給Slave,也就是沒有象MySQL那樣有複製位置的概念,即無差異複寫,如果一個master串連多個slave,就會比較影響master效能了。
資料備份策略
具體的備份策略是可以很靈活的,比如可以大致如下:
- 為了提高master的效能關閉master的持久化機制,即不進行快照也不進行aof,而是在淩晨訪問量低的時候定時的用bgsave命令進行快照,並將快照檔案儲存到備份伺服器上;
- slave端開啟aof機制,並定時的用bgrewriteaof 進行資料壓縮,將壓縮後的資料檔案儲存到備份伺服器上;
- 定時的檢查master與slave上的資料是否一致;
- 當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 1save 300 10save 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 changedredis也可以關閉自動持久化,注釋掉這些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 alwaysappendfsync everysec# appendfsync no 當配置為always的時候,每次server.aofbuf中的資料寫入到檔案之後,才會返回給用戶端,這樣可以保證資料不丟,但是頻繁的IO操作,會降低效能。 everysec每秒寫一次,這可能會丟失一秒內的操作。 aof最大的問題就是隨著時間append file會變的很大,所以我們需要bgrewriteaof命令重新整理檔案,只保留最新的kv資料。