詳解:Redis主從技術的應用
在生產環境中,Redis通過持久化功能(RDB和AOF技術)保證了即使在服務器重啟的情況下也不會損失(或少量損失)數據。但是由於數據是存儲在一臺服務器上的,如果這臺服務器出現硬盤故障等問題(生產環境中多次遇到),也會導致數據丟失,為了避免單點故障,通常的做法是將數據庫復制多個副本以部署在不同的服務器上,這樣即使有一臺服務器出現故障,其他服務器依然可以以最快的速度提供服務。為此,Redis提供了復制(replication)功能,可以實現當一臺數據庫中的數據更新後,自動將更新的數據同步到其他數據庫上。
在復制的概念中,數據庫分為兩類,一類是主數據庫(master),另一類是從數據庫(slave)。主數據庫可以進行讀寫操作,當寫操作導致數據變化時自動將數據同步給從數據庫。而從數據庫一般是只讀的,並接受主數據庫同步過來的數據。一個主數據庫可以擁有多個從數據庫。
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對話與重復的排序操作,有多份數據冗余就相對簡單了)。
通過復制可以避免master全量寫硬盤的消耗:只要配置 master 的配置文件redis.conf來“避免保存”(註釋掉所有”save”命令),然後連接一個用來持久化數據的slave即可。但是這樣要確保masters 不會自動重啟(更多內容請閱讀下段)
Redis復制配置
在Redis中使用復制功能非常容易,只需要在從數據庫的配置文件中加入“slaveof 主數據復制 主數據庫端口”即可。
主數據庫無需進行任何配置。下面先來看看一個最簡化的復制系統,我們在一臺服務器上啟動兩個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過程中有很大的幫助,包括如何規劃節點,如果處理節點故障等。下面介紹Redis實現復制的工程。
詳解:Redis主從技術的應用
當一個從數據庫啟動後,會向主數據庫發送SYNC命令,同時主數據庫接收到SYNC命令後會開始在後臺保存快照(即RDB持久化的過程),並將保存快照期間接收到的命令緩存起來,當快照完成後,Redis Master會將快照文件發送給從數據庫,從數據庫收到後,會載入快照文件。之後Redis Master會以Redis命令協議的格式,將寫命令緩沖區中積累的所有內容都發送給從服務器。以上過程稱為復制初始化,復制初始化結束後,主數據庫每當收到寫命令時就會將命令同步給從數據庫,從而保證主從數據庫數據一致。
你可以通過telnet命令來親自驗證這個同步過程:首先連上一個正在處理命令請求的Redis服務器,然後向它發送SYNC命令,過一陣子,你將會看到telnet會話接收到服務器發來的大段數據(.rdb文件),之後還會看到,所有的服務器執行過的寫命令,都會重新發送到telnet會話來。
當主從數據庫之間的連接斷開重連後,Redis 2.6以及之前的版本會重新進行復制初始化(即主數據庫重新保存快照並傳送給從數據庫),即使從數據庫可以僅有幾條命令沒有收到,主數據庫也必須要將數據庫裏的所有數據重新傳送給從數據庫。這使得主從數據庫斷線重連後的數據恢復過程效率很低下,在網絡環境不好的時候這一問題尤其明顯,Redis 2.8版本的一個重要改進就是斷線重連能夠支持有條件的增量數據傳輸,當從數據庫重新連接上主數據庫後,主數據庫只需要將斷線期間執行的命令傳送給從數據庫,從而大大提高Redis復制的實用性。
增量復制的實現,基於以下3點:
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”指令。
點擊鏈接加入群聊【java架構師】:https://jq.qq.com/?_wv=1027&k=5yoRu9e
詳解:Redis主從技術的應用