有時候在主服務(wù)器事實上沒有任何故障的情況下強制一次故障轉(zhuǎn)移是很有用的。例如,為了升級主服務(wù)器節(jié)點中的一個進程,可以對其進行故障轉(zhuǎn)移使其變?yōu)橐粋€從服務(wù)器,這樣最小化了對可用性的影響。
Redis 集群支持使用 CLUSTER FAILOVER 命令來手動故障轉(zhuǎn)移,必須在你想進行故障轉(zhuǎn)移的主服務(wù)的其中一個從服務(wù)器上執(zhí)行。
手動故障轉(zhuǎn)移很特別,和真正因為主服務(wù)器失效而產(chǎn)生的故障轉(zhuǎn)移要更安全,因為采取了避免過程中數(shù)據(jù)丟失的方式,僅當(dāng)系統(tǒng)確認新的主服務(wù)器處理完了舊的主服務(wù)器的復(fù)制流時,客戶端才從原主服務(wù)器切換到新主服務(wù)器。
下面是當(dāng)你手動故障轉(zhuǎn)移時你從從服務(wù)器日志中看到的內(nèi)容:
# Manual failover user request accepted.
# Received replication offset for paused master manual failover: 347540
# All master replication stream processed, manual failover can start.
# Start of election delayed for 0 milliseconds (rank #0, offset 347540).
# Starting a failover election for epoch 7545.
# Failover election won: I'm the new master.
基本上,連接到我們正在故障轉(zhuǎn)移的主服務(wù)器的客戶端停止了。與此同時,主服務(wù)器發(fā)送復(fù)制偏移量給從服務(wù)器,等待到達這個偏移量。當(dāng)復(fù)制偏移量到達以后,故障轉(zhuǎn)移就開始了,舊的主服務(wù)器被通知切換配置。當(dāng)客戶端在舊主服務(wù)器上解除阻塞時,就被重定向到新的主服務(wù)器。
添加一個新節(jié)點的過程基本上就是,添加一個空節(jié)點,然后,如果是作為主節(jié)點則移動一些數(shù)據(jù)進去,如果是從節(jié)點則其作為某個節(jié)點的副本。
兩種情況我們都會討論,先從添加一個新的主服務(wù)器實例開始。
兩種情況下,第一步要完成的都是添加一個空節(jié)點。
我們使用與其他節(jié)點相同的配置(端口號除外)在 7006 端口(我們已存在的 6 個節(jié)點已經(jīng)使用了從 7000 到 7005 的端口)上開啟一個新的節(jié)點,那么為了與我們之前的節(jié)點布局一致,你得這么做:
此時服務(wù)器已經(jīng)在運行中了。
現(xiàn)在我們可以像通常一樣使用 redis-trib 來添加節(jié)點到已存在的集群中。
./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000
你可以看到,我使用了 addnode 命令,指定新的節(jié)點地址為第一個參數(shù),集群中一個隨機存在的節(jié)點的地址作為第二個參數(shù)。
實際上 redis-trib 在這里對我們只有很少的幫助,只是發(fā)送了一個 CLUSTER MEET 消息到節(jié)點,這些也可以手動完成。但是 redis-trib 也在操作之前檢查了集群的狀態(tài),所以即便你知道內(nèi)部是如何工作的,一直通過 redis-trib 來執(zhí)行集群操作也是一個不錯的主意。
現(xiàn)在,我們可以連接到這個新的節(jié)點,看看它是否真的加入到了集群中:
redis 127.0.0.1:7006> cluster nodes
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385543178575 0 connected 5960-10921
3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385543179583 0 connected
f093c80dde814da99c5cf72a7dd01590792b783b :0 myself,master - 0 0 0 connected
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543178072 3 connected
a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385543178575 0 connected
97a3a64667477371c4479320d683e4c8db5858b1 127.0.0.1:7000 master - 0 1385543179080 0 connected 0-5959 10922-11422
3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385543177568 3 connected 11423-16383
注意,因為這個節(jié)點已經(jīng)連接到集群了,所以也已經(jīng)可以正確地重定向客戶端查詢,簡而言之,這個節(jié)點已經(jīng)是集群的一部分了。但是它比其他主節(jié)點有兩個特殊之處:
現(xiàn)在可以使用 redis-trib 的重新分片特性來給這個節(jié)點賦予哈希槽了?;旧蠜]有必現(xiàn)展示這個了,因為我們已經(jīng)在之前的小節(jié)中展示過了,沒有什么不同,只是以空節(jié)點為目標(biāo)的一次重分片。
添加一個新副本可以有兩種方式。顯而易見的一種方式是再次使用 redis-trib,但是要使用—slave 選項,像這樣:
./redis-trib.rb add-node --slave 127.0.0.1:7006 127.0.0.1:7000
注意,這里的命令行完全像我們在添加一個新主服務(wù)器時使用的一樣,所以我們沒有指定要給哪個主服務(wù)器添加副本。這種情況下,redis-trib 會添加一個新節(jié)點作為一個具有較少副本的隨機的主服務(wù)器的副本。
但是,你可以使用下面的命令行精確地指定你想要的主服務(wù)器作為副本的目標(biāo):
./redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000
這樣我們就把一個新的副本賦予了一個指定的主服務(wù)器。
一種更手工的給指定主服務(wù)器添加副本的方式,是添加一個新節(jié)點作為一個空主服務(wù)器,然后使用 CLUSTER REPLICATE 命令將其變?yōu)楦北?。如果?jié)點被作為從服務(wù)器添加,但是你想移動它為另一個不同的主服務(wù)器的副本,這也是可行的。
例如,為了給節(jié)點 127.0.0.1:7005 添加一個副本,這個節(jié)點當(dāng)前服務(wù) 11432-16383 范圍內(nèi)的哈希槽,其節(jié)點 ID 為 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,所有我們需要去做的,就是連接這個新的節(jié)點(已經(jīng)作為空主服務(wù)器被添加)然后發(fā)送命令:
redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
就是這樣?,F(xiàn)在我們有了這組哈希槽的一個新副本了,集群中的其它節(jié)點也已經(jīng)知道了(需要幾秒鐘來更新配置)。我們可以用下面的命令來核實一下:
$ redis-cli -p 7000 cluster nodes | grep slave | grep 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
f093c80dde814da99c5cf72a7dd01590792b783b 127.0.0.1:7006 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617702 3 connected
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617198 3 connect
這個 3c3a0c…的節(jié)點現(xiàn)在有兩個從服務(wù)器了,分別運行于 7002(已存在的)和 7006(新的)端口。
要移除一個從服務(wù)器節(jié)點,只要使用 redis-trib 的 del-node 命令就可以:
./redis-trib del-node 127.0.0.1:7000 <node-id>
第一個參數(shù)只是集群中的一個隨機節(jié)點,第二個參數(shù)是你想移除的節(jié)點的 ID。
你也可以用同樣的方式移除一個主服務(wù)器節(jié)點,但是,為了移除一個主服務(wù)器節(jié)點,它必須是空的。如果主服務(wù)器不是空的,你需要先將其數(shù)據(jù)重分片到其他的主服務(wù)器節(jié)點。
另一種移除主服務(wù)器節(jié)點的方式,就是在其從服務(wù)器上執(zhí)行一次手工故障轉(zhuǎn)移,當(dāng)它變?yōu)榱诵碌闹鞣?wù)器的從服務(wù)器以后將其移除。顯然,當(dāng)你需要真的減少你的集群中的主服務(wù)器的數(shù)量時這個沒有什么幫助,如果那樣的話,就需要重新分片了。
在 Redis 集群中,可以使用下面的命令在任何時候重新配置一個從服務(wù)器復(fù)制一個不同的主服務(wù)器:
CLUSTER REPLICATE <master-node-id>
但是有一種特殊的場景,你想讓副本集自動地從一個主服務(wù)器移動到另一個主服務(wù)器,而不需要系統(tǒng)管理員的幫助。自動重新配置副本集被稱為副本集遷移,這可以改善 Redis 集群的可靠性。
注意:你可以在 Redis 集群規(guī)范中閱讀到副本集遷移的細節(jié),這里我們只提供一般性的信息,以及為了從中受益你該做什么。
為什么你想讓你的集群副本在某些特定條件下從一個主服務(wù)器移動到另一個的原因,是通常情況下 Redis 集群對失敗的抵御能力和連接到指定從服務(wù)器的副本數(shù)量成正相關(guān)。
例如,每個主服務(wù)器只有單個副本組成的集群在主服務(wù)器及其副本同時失效時就不能夠繼續(xù)運轉(zhuǎn),因為沒有其他的實例擁有這臺主服務(wù)器服務(wù)的哈希槽的副本。但是,網(wǎng)絡(luò)斷裂可能會在同一時間隔絕若干節(jié)點,其他類型的故障,例如單個節(jié)點的硬件或者軟件錯誤,是值得注意的一類故障,很可能會同時發(fā)生,所以在每個主服務(wù)器擁有一個從服務(wù)器的集群中,有可能從服務(wù)器在下午 4 點被干掉,而主服務(wù)器在下午 6 點被干掉。這仍然會導(dǎo)致集群不再能運轉(zhuǎn)。
為了改進系統(tǒng)的可靠性,我們有一些增加額外副本集到每個主服務(wù)器的選項,但是這代價昂貴。副本遷移允許添加更多的從服務(wù)器到少許的主服務(wù)器。所以你可以有 10 個主服務(wù)器,每個有一個從服務(wù)器,總共 20 個實例。但是你為某些主服務(wù)器添加例如 3 個以上的實例,那么有些主服務(wù)器會有多余一個的從服務(wù)器。
有了副本集遷移會發(fā)生什么?如果一個主服務(wù)器沒有從服務(wù)器,一個來自于擁有多個從服務(wù)器的主服務(wù)器上的從服務(wù)器會遷移到這個孤獨的主服務(wù)器上。所以像上面我們舉的例子中,在你的從服務(wù)器下午 4 點下線以后,另一個從服務(wù)器會接替它的位置,當(dāng)主服務(wù)器在下午 5 點也失效的時候,仍然有一個從服務(wù)器可以被選舉,這樣集群就可以繼續(xù)運轉(zhuǎn)了。
那么,簡而言之,你應(yīng)該了解副本集遷移的什么呢?
升級從服務(wù)器節(jié)點很簡單,因為你只需要停止節(jié)點然后用已更新的 Redis 版本重啟。如果有客戶端使用從服務(wù)器節(jié)點分離讀請求,它們應(yīng)該能夠在某個節(jié)點不可用時重新連接另一個從服務(wù)器。
升級主服務(wù)器要稍微復(fù)雜一些,建議的步驟是:
你可以按照這些步驟來一個節(jié)點一個節(jié)點的升級,直到全部節(jié)點升級完畢。
想遷移到 Redis 集群的用戶可能只有一個單一的主服務(wù)器,或者已經(jīng)使用了已存在的分片布局,通過使用某種內(nèi)部算法,或者他們的客戶端庫實現(xiàn)的分片算法,或者 Redis 代理,鍵被分拆到 N 個節(jié)點上。
這兩種情況下遷移到 Redis 集群都很簡單,但是最重要的細節(jié)是,如果程序使用了多鍵操作,怎么辦。有三種不同的情況:
Redis 不處理第三種情況:應(yīng)用程序需要被修改為不能使用多鍵操作,或者只能在相同的哈希標(biāo)簽上下文中使用。
前兩種情況覆蓋到了,所以我們會聚焦在這兩種情況,它們會用相同的方式來處理,所以本文不會去區(qū)別對待。
假設(shè)你的已存在數(shù)據(jù)集已經(jīng)被拆分到了 N 個主服務(wù)器上,如果你沒有已存在的分片的話 N=1,你需要下面的步驟來遷移你的數(shù)據(jù)集到 Redis 集群:
還有一個方式從外部實例導(dǎo)入數(shù)據(jù)到 Redis 集群,就是使用 redis-trib import 命令。
這個命令移動一個運行實例(同時刪除源實例上的鍵)上的所有鍵到一個指定已存在的 Redis 集群。但是,注意如果你使用 Redis 2.8 實例作為來源實例,操作可能很慢,因為 2.8 沒有實現(xiàn)遷移連接緩存(migrate connection caching),所以在執(zhí)行這個操作之前,你可能得重啟你的 Redis 3.x 版本的源實例。