在生產環境中,Redis經過持久化功能(RDB和AOF技術)保證了即便在服務器重啓的狀況下也不會損失(或少許損失)數據。可是因爲數據是存儲在一臺服務器上的,若是這臺服務器出現硬盤故障等問題(生產環境中屢次遇到),也會致使數據丟失,爲了不單點故障,一般的作法是將數據庫複製多個副本以部署在不一樣的服務器上,這樣即便有一臺服務器出現故障,其餘服務器依然能夠以最快的速度提供服務。爲此,Redis提供了複製(replication)功能,能夠實現當一臺數據庫中的數據更新後,自動將更新的數據同步到其餘數據庫上。node
在複製的概念中,數據庫分爲兩類,一類是主數據庫(master),另外一類是從數據庫(slave)。主數據庫能夠進行讀寫操做,當寫操做致使數據變化時自動將數據同步給從數據庫。而從數據庫通常是隻讀的,並接受主數據庫同步過來的數據。一個主數據庫能夠擁有多個從數據庫。redis
Redis複製很簡單易用,它經過配置容許slave Redis Servers或者Master Servers的複製品。接下來有幾個關於redis複製的很是重要特性:數據庫
一個Master能夠有多個Slaves。緩存
Slaves能經過和其餘slave的連接,除了能夠接受同一個master下面slaves的連接之外,還能夠接受同一個結構圖中的其餘slaves的連接。服務器
redis複製是在master段是非阻塞的,這就意味着master在同一個或多個slave端執行同步的時候還能夠接受查詢。網絡
複製在slave端也是非阻塞的,假設你在redis.conf中配置redis這個功能,當slave在執行的新的同步時,它仍能夠用舊的數據信息來提供查詢,不然,你能夠配置當redis slaves去master失去聯繫是,slave會給發送一個客戶端錯誤。架構
爲了有多個slaves能夠作只讀查詢,複製能夠重複2次,甚至屢次,具備可擴展性(例如:slaves對話與重複的排序操做,有多份數據冗餘就相對簡單了)。less
經過複製能夠避免master全量寫硬盤的消耗:只要配置 master 的配置文件redis.conf來「避免保存」(註釋掉全部」save」命令),而後鏈接一個用來持久化數據的slave便可。可是這樣要確保masters 不會自動重啓(更多內容請閱讀下段)運維
在Redis中使用複製功能很是容易,只須要在從數據庫的配置文件中加入「slaveof 主數據複製 主數據庫端口」便可。socket
主數據庫無需進行任何配置。下面先來看看一個最簡化的複製系統,咱們在一臺服務器上啓動兩個redis示例,監聽在不一樣的端口,其中一個做爲主數據庫,另外一個做爲從數據庫。
首先咱們不加任何參數來啓動一個redis實例做爲主數據庫:
$ redis-server --port 6379 &
該實例默認監聽6379端口,而後加上slaveof參數啓動另外一個redis實例做爲從數據庫,並讓其監聽6380端口:
$ redis-server --port 6380 --slaveof 127.0.0.1 6379 &
查看一下實例的啓動狀況:
$ ps aux | grep redis root 2886 0.0 0.0 38652 4448 pts/0 Sl 16:57 0:00 redis-server *:6379 root 2889 0.0 0.0 36604 4368 pts/0 Sl 16:57 0:00 redis-server *:6380
此時在主數據庫中的任何數據變化都會自動同步到從數據庫中,咱們打開redis-cli實例A並鏈接到數據庫:
$ redis-cli -p 6379
再打開redis-cli實例B並鏈接到從數據庫:
$ redis-cli -p 6380
這時咱們使用INFO命令來分別在實例A和實例B中獲取replication的相關信息。
127.0.0.1:6379> INfo replication # Replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=266,lag=1 master_repl_offset:266 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:265
能夠看到,實例A的角色(role)是master,即主數據庫,同時已鏈接的從數據庫(connectd_slaves)的個數爲1個。
一樣在實例B中獲取響應的信息爲:
127.0.0.1:6380> INfo replication # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:9 master_sync_in_progress:0 slave_repl_offset:378 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
能夠看到,實例B的role是slave,即從數據庫,同時其主數據庫的地址爲127.0.0.1,端口爲6379。
而後咱們在實例A中使用SET命令設置一個鍵的值:
127.0.0.1:6379> set foo bar OK
此時在實例B中就能夠得到該值了:
127.0.0.1:6380> get foo "bar"
證實兩個Redis實例的複製功能已經可用了。默認狀況,從數據庫是隻讀的,若是直接修改從數據庫的數據會出現錯誤,以下:
127.0.0.1:6380> set foo hey (error) READONLY You can't write against a read only slave.
但也能夠經過設置從數據庫的配置文件中的slave-read-only=no,以使從數據庫可寫,可是由於對從數據庫的任何更改都不會同步給任何其餘數據庫,而且一旦主數據庫中的更新了賭贏的數據就會覆蓋從數據庫中的改動,因此一般場景下不該該設置從數據庫可寫,以避免致使易被忽略的潛在應用邏輯錯誤。
配置多臺從數據庫的方法也同樣,在全部的從數據庫的配置文件中都加上salveof參數指向同一個主數據庫便可。除了經過配置文件或命令行參數設置slaveof參數外,還能夠在運行時使用slaveof命令修改,下面咱們再添加一個實例C(6381):
$ redis-server --port 6381 & $ redis-cli -p 6381 127.0.0.1:6381> slaveof 127.0.0.1 6379 127.0.0.1:6381> get foo "bar"
若是該數據庫已是其餘主數據庫的從數據庫了,slaveof命令會中止和原來數據庫的同步轉而和新數據庫同步,此外對於從數據庫來講,還可使用slaveof no one命令來使當前數據庫中止接收其餘數據庫的同步並轉換成爲主數據庫。以下測試,在從庫實例C上寫入數據時時不容許的,而後使用slaveof no one將此數據庫轉換爲主數據庫,而後再寫入數據就沒有問題了。通常用於但主節點掛掉的時候,馬上把從節點切換爲主節點提供數據操做服務。
127.0.0.1:6381> set foo bar (error) READONLY You can't write against a read only slave. 127.0.0.1:6381> SLAVEOF no one 5493:M 07 Aug 17:23:06.792 # Connection with master lost. 5493:M 07 Aug 17:23:06.792 * Caching the disconnected master state. 5493:M 07 Aug 17:23:06.792 * Discarding previously cached master state. 5493:M 07 Aug 17:23:06.792 * MASTER MODE enabled (user request from 'id=2 addr=127.0.0.1:40825 fd=6 name= age=348 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=slaveof') 2886:M 07 Aug 17:23:06.792 # Connection with slave 127.0.0.1:6381 lost. OK 127.0.0.1:6381> set foo bar OK
複製經常使用參數
slaveof
將當前server作爲slave,併爲其指定master信息。
masterauth
以認證的方式鏈接到master,若是master中使用了」密碼保護」,slave必須交付正確的受權密碼,才能鏈接成功。」requirepas」配置項指定了當前server的密碼。此配置項中值須要和master機器的」requirepas」保持一致。此參數配置在slave端。
slave-serve-stale-data yes
若是當前server是slave,那麼當slave與master失去通信時,是否繼續爲客戶端提供服務,」yes」表示繼續,」no」表示終止。在」yes」狀況下,slave繼續向客戶端提供只讀服務,有可能此時的數據已通過期。在」no」狀況下,任何向此server發送的數據請求服務(包括客戶端和此server的slave)都將被告知」error」。
slave-read-only yes slave是否爲」只讀」,強烈建議爲」yes」。 repl-ping-slave-period 10 slave向指定的master發送ping消息的時間間隔(秒),默認爲10。 repl-timeout 60 slave與master通信中,最大空閒時間,默認60秒,超時將致使鏈接關閉。 repl-disable-tcp-nodelay no slave與master的鏈接,是否禁用TCP nodelay選項。」yes」表示禁用,那麼socket通信中數據將會以packet方式發送(packet大小受到socket buffer限制),能夠提升socket通信的效率(tcp交互次數),可是小數據將會被buffer,不會被當即發送,對於接受者可能存在延遲。」no」表示開啓tcp nodelay選項,任何數據都會被當即發送,及時性較好,可是效率較低。建議爲」no」。 slave-priority 100 適用Sentinel模塊(unstable,M-S集羣管理和監控),須要額外的配置文件支持。slave的權重值,默認100。當master失效後,Sentinel將會從slave列表中找到權重值最低(>0)的slave,並提高爲master。若是權重值爲0,表示此slave爲」觀察者」,不參與master選舉。
瞭解Redis複製的原理對運維Redis過程當中有很大的幫助,包括如何規劃節點,若是處理節點故障等。下面介紹Redis實現複製的工程。
當一個從數據庫啓動後,會向主數據庫發送SYNC命令,同時主數據庫接收到SYNC命令後會開始在後臺保存快照(即RDB持久化的過程),並將保存快照期間接收到的命令緩存起來,當快照完成後,Redis Master會將快照文件發送給從數據庫,從數據庫收到後,會載入快照文件。以後Redis Master會以Redis命令協議的格式,將寫命令緩衝區中積累的全部內容都發送給從服務器。以上過程稱爲複製初始化,複製初始化結束後,主數據庫每當收到寫命令時就會將命令同步給從數據庫,從而保證主從數據庫數據一致。
你能夠經過telnet命令來親自驗證這個同步過程:首先連上一個正在處理命令請求的Redis服務器,而後向它發送SYNC命令,過一陣子,你將會看到telnet會話接收到服務器發來的大段數據(.rdb文件),以後還會看到,全部的服務器執行過的寫命令,都會從新發送到telnet會話來。
當主從數據庫之間的鏈接斷開重連後,Redis 2.6以及以前的版本會從新進行復制初始化(即主數據庫從新保存快照並傳送給從數據庫),即便從數據庫能夠僅有幾條命令沒有收到,主數據庫也必需要將數據庫裏的全部數據從新傳送給從數據庫。這使得主從數據庫斷線重連後的數據恢復過程效率很低下,在網絡環境很差的時候這一問題尤爲明顯,Redis 2.8版本的一個重要改進就是斷線重連可以支持有條件的增量數據傳輸,當從數據庫從新鏈接上主數據庫後,主數據庫只須要將斷線期間執行的命令傳送給從數據庫,從而大大提升Redis複製的實用性。
1)從數據庫會存儲主數據庫的運行ID(run id),每一個Redis運行實例均會擁有一個惟一的運行ID,每當實例重啓後,就會自動生成一個新的運行ID。
2)在複製同步階段,主數據庫每將一個命令傳送給從數據庫時,都會同時把該命令存放到一個積壓隊列(backlog)中,並記錄下當前積壓隊列中存放的命令的偏移量範圍。
3)同時,從數據庫接收到主數據庫傳來的命令時,會記錄下該命令的偏移量。
這三點是實現增量複製的基礎,當主從鏈接準備就緒後,從數據庫會發送一條SYNC命令來告訴主數據庫能夠開始把全部數據同步過來了。而2.8版本以後,再也不發送SYNC命令,取而代之的是發送PSYNC,格式爲「PSYNC 主數據庫的運行ID 斷開前最新的命令偏移量」。主數據庫收到PSYNC命令後,會執行如下判斷來決定這次重連是否能夠執行增量複製。
1)首先主數據庫會判斷從數據庫傳送來的運行ID是否和本身的運行ID相同,這一步驟的意義在於確保從數據庫以前確實是和本身同步的,以避免從數據庫拿到錯誤的數據(如主數據庫在斷連期間重啓過,會形成數據的不一致性)。
2)而後判斷從數據庫最後同步成功的命令偏移量是否在積壓隊列中,若是在則能夠執行增量複製,並將積壓隊列中相應的命令發送給從數據庫。
若是這次重連不知足增量複製的條件,主數據會進行一次所有同步(即與Redis 2.6的過程相同),大部分狀況下,增量複製的過程對開發者來講是徹底透明的,開發者不須要關心增量複製的具體細節,2.8版本的主數據庫也能夠正常地和舊版本的從數據庫同步(經過接收SYNC命令),一樣2.8版本的從數據庫也能夠與舊版本的主數據庫同步(經過發送SYNC命令),惟一須要開發者設置的就是積壓隊列的大小了。
積壓隊列在本質上是一個固定長度的循環隊列,默認狀況下積壓隊列的大小爲1MB,能夠經過配置文件的repl-backlog-size選項來調整。很容易理解的是,積壓隊列越大,其容許的主從數據庫斷線的時間就越長。根據主從數據庫之間的網絡狀態,設置一個合理的積壓隊列很重要。由於積壓隊列存儲的內容是命令自己,如 SET FOO BAR,因此估算積壓隊列的大小隻須要估計主從數據庫斷線的時間中主從數據庫可能執行的命令的大小便可。與積壓隊列相關的另外一個配置選項是repl-backlog-ttl,即當全部主從數據庫與主數據斷開鏈接後,通過多久時間能夠釋放積壓隊列的內存空間,默認時間是1小時。
另外一個相對耗時的操做是持久化,爲了提升性能,能夠經過複製功能創建一個或多個從數據庫,並在從數據庫中啓用持久化,同時在主數據庫禁用持久化,當從數據庫崩潰重啓後主數據庫會自動將時間同步過來,因此無需擔憂數據丟失。
而後當主數據庫崩潰時,狀況就稍顯複雜了。手工經過從數據庫數據恢復主數據庫數據時,須要嚴格按照如下兩步進行:
1)在從數據庫中使用SLAVEOF NO ONE命令將從數據庫提高爲主數據庫繼續服務。
2)啓動以前崩潰的主數據庫,而後使用SLAVEOF命令將其設置成新的主數據庫的從數據庫,便可將數據同步回來。
注意,當開啓複製且主數據庫關閉持久化的時候,必定不要使用supervisor以及相似的進程管理工具令主數據庫崩潰後自動重啓。一樣當主數據庫所在的服務器因故關閉時,也要避免直接從新啓動。這是由於當主數據庫從新啓動後,由於沒開持久化功能,因此數據庫中全部數據都被清空,這時從數據庫依然會從主數據庫中接收數據,使得全部從數據庫也被清空,致使從數據庫的持久化失去意義。
不管哪一種狀況,手工維護從數據庫或主數據的重啓以及數據恢復都相對麻煩,好在Redis提供了一種自動化方案哨兵來實現這一過程,避免了手工維護的麻煩和容易出錯的問題。
上面介紹了Redis複製的工做原理時介紹了複製是基於RDB方式的持久化實現的,即主數據庫端在後臺保存了RDB快照,從數據庫端則接收並載入快照文件,這樣的實現有點是能夠顯著地簡化邏輯,複用已有的代碼,可是缺點也很明顯。
1)當主數據庫禁用RDB快照時(即刪除了全部的配置文件中的save語句),若是執行了複製初始化操做,Redis依然會生成RDB快照,因此下次啓動後主數據庫會以該快照恢復數據。由於複製發生的時間不能肯定,這使得恢復的數據多是任什麼時候間點的。
2)由於複製初始化時須要在硬盤中建立RDB快照文件,因此若是硬盤性能很慢時這一過程會對性能產生影響。舉例來講,當使用Redis作緩存系統時,由於不須要持久化,因此服務器的硬盤讀寫速度可能較差。可是當該緩存系統使用一主多從的集羣架構時,每次和從數據庫同步,Redis都會執行一次快照,同時對硬盤進行讀寫,致使性能降低。
所以從2.8.18版本開始,Redis引入了無硬盤複製選項,開啓該選項時,Redis在與從數據庫進行復制初始化時將不會將快照內容存儲到硬盤上,而是直接經過網絡發送給從數據庫,避免了硬盤的性能瓶頸。能夠在配置文件中使用以下配置來開啓該功能:
repl-diskless-sync yes
PS:當須要把Slave轉換爲Master時可使用」SLAVEOF ON ONE」指令。