Redis:史上最强【集群】入门实践教程
來源:我沒有三顆心臟
一、Redis 集群概述
Redis 主從復(fù)制
到 目前 為止,我們所學(xué)習(xí)的 Redis 都是 單機版 的,這也就意味著一旦我們所依賴的 Redis 服務(wù)宕機了,我們的主流程也會受到一定的影響,這當(dāng)然是我們不能夠接受的。
所以一開始我們的想法是:搞一臺備用機。這樣我們就可以在一臺服務(wù)器出現(xiàn)問題的時候切換動態(tài)地到另一臺去:
幸運的是,兩個節(jié)點數(shù)據(jù)的同步我們可以使用 Redis 的 主從同步 功能幫助到我們,這樣一來,有個備份,心里就踏實多了。
Redis 哨兵
后來因為某種神秘力量,Redis 老會在莫名其妙的時間點出問題 (比如半夜 2 點),我總不能 24 小時時刻守在電腦旁邊切換節(jié)點吧,于是另一個想法又開始了:給所有的節(jié)點找一個 "管家",自動幫我監(jiān)聽照顧節(jié)點的狀態(tài)并切換:
這大概就是 Redis 哨兵 (Sentinel) 的簡單理解啦。什么?管家宕機了怎么辦?相較于有大量請求的 Redis 服務(wù)來說,管家宕機的概率就要小得多啦.. 如果真的宕機了,我們也可以直接切換成當(dāng)前可用的節(jié)點保證可用..
Redis 集群化
好了,通過上面的一些解決方案我們對 Redis 的 穩(wěn)定性 稍微有了一些底氣了,但單臺節(jié)點的計算能力始終有限,所謂人多力量大,如果我們把 多個節(jié)點組合 成 一個可用的工作節(jié)點,那就大大增加了 Redis 的 ?高可用、可擴展、分布式、容錯 等特性:
二、主從復(fù)制
主從復(fù)制,是指將一臺 Redis 服務(wù)器的數(shù)據(jù),復(fù)制到其他的 Redis 服務(wù)器。前者稱為 主節(jié)點(master),后者稱為 從節(jié)點(slave)。且數(shù)據(jù)的復(fù)制是 單向 的,只能由主節(jié)點到從節(jié)點。Redis 主從復(fù)制支持 主從同步 和 從從同步 兩種,后者是 Redis 后續(xù)版本新增的功能,以減輕主節(jié)點的同步負擔(dān)。
主從復(fù)制主要的作用
數(shù)據(jù)冗余: 主從復(fù)制實現(xiàn)了數(shù)據(jù)的熱備份,是持久化之外的一種數(shù)據(jù)冗余方式。
故障恢復(fù): 當(dāng)主節(jié)點出現(xiàn)問題時,可以由從節(jié)點提供服務(wù),實現(xiàn)快速的故障恢復(fù) (實際上是一種服務(wù)的冗余)。
負載均衡: 在主從復(fù)制的基礎(chǔ)上,配合讀寫分離,可以由主節(jié)點提供寫服務(wù),由從節(jié)點提供讀服務(wù) (即寫 Redis 數(shù)據(jù)時應(yīng)用連接主節(jié)點,讀 Redis 數(shù)據(jù)時應(yīng)用連接從節(jié)點),分擔(dān)服務(wù)器負載。尤其是在寫少讀多的場景下,通過多個從節(jié)點分擔(dān)讀負載,可以大大提高 Redis 服務(wù)器的并發(fā)量。
高可用基石: 除了上述作用以外,主從復(fù)制還是哨兵和集群能夠?qū)嵤┑?基礎(chǔ),因此說主從復(fù)制是 Redis 高可用的基礎(chǔ)。
■快速體驗
在 Redis 中,用戶可以通過執(zhí)行 SLAVEOF 命令或者設(shè)置 slaveof 選項,讓一個服務(wù)器去復(fù)制另一個服務(wù)器,以下三種方式是 完全等效 的:
配置文件:在從服務(wù)器的配置文件中加入:slaveof <masterip> <masterport>
啟動命令:redis-server 啟動命令后加入 --slaveof <masterip> <masterport>
客戶端命令:Redis 服務(wù)器啟動后,直接通過客戶端執(zhí)行命令:slaveof <masterip> <masterport>,讓該 Redis 實例成為從節(jié)點。
需要注意的是:主從復(fù)制的開啟,完全是在從節(jié)點發(fā)起的,不需要我們在主節(jié)點做任何事情。
第一步:本地啟動兩個節(jié)點
在正確安裝好 Redis 之后,我們可以使用 redis-server --port <port> 的方式指定創(chuàng)建兩個不同端口的 Redis 實例,例如,下方我分別創(chuàng)建了一個 6379 和 6380 的兩個 Redis 實例:
# 創(chuàng)建一個端口為 6379 的 Redis 實例 redis-server --port 6379 # 創(chuàng)建一個端口為 6380 的 Redis 實例 redis-server --port 6380此時兩個 Redis 節(jié)點啟動后,都默認為 主節(jié)點。
第二步:建立復(fù)制
我們在 6380 端口的節(jié)點中執(zhí)行 slaveof 命令,使之變?yōu)閺墓?jié)點:
# 在 6380 端口的 Redis 實例中使用控制臺 redis-cli -p 6380 # 成為本地 6379 端口實例的從節(jié)點 127.0.0.1:6380> SLAVEOF 127.0.0.1? 6379 OK第三步:觀察效果
下面我們來驗證一下,主節(jié)點的數(shù)據(jù)是否會復(fù)制到從節(jié)點之中:
先在 從節(jié)點 中查詢一個 不存在 的 key:
再在 主節(jié)點 中添加這個 key:
此時再從 從節(jié)點 中查詢,會發(fā)現(xiàn)已經(jīng)從 主節(jié)點 同步到 從節(jié)點:
第四步:斷開復(fù)制
通過 slaveof <masterip> <masterport> 命令建立主從復(fù)制關(guān)系以后,可以通過 slaveof no one 斷開。需要注意的是,從節(jié)點斷開復(fù)制后,不會刪除已有的數(shù)據(jù),只是不再接受主節(jié)點新的數(shù)據(jù)變化。
從節(jié)點執(zhí)行 slaveof no one 之后,從節(jié)點和主節(jié)點分別打印日志如下:、
# 從節(jié)點打印日志 61496:M 17 Mar 2020 08:10:22.749 # Connection with master lost. 61496:M 17 Mar 2020 08:10:22.749 * Caching the disconnected master state. 61496:M 17 Mar 2020 08:10:22.749 * Discarding previously cached master state. 61496:M 17 Mar 2020 08:10:22.749 * MASTER MODE enabled (user request from 'id=4 addr=127.0.0.1:55096 fd=8 name= age=1664 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=34 qbuf-free=32734 obl=0 oll=0 omem=0 events=r cmd=slaveof')# 主節(jié)點打印日志 61467:M 17 Mar 2020 08:10:22.749 # Connection with replica 127.0.0.1:6380 lost.■實現(xiàn)原理簡析
為了節(jié)省篇幅,我把主要的步驟都 濃縮 在了上圖中,其實也可以 簡化成三個階段:準備階段-數(shù)據(jù)同步階段-命令傳播階段。下面我們來進行一些必要的說明。
身份驗證 | 主從復(fù)制安全問題
在上面的 快速體驗 過程中,你會發(fā)現(xiàn) slaveof 這個命令居然不需要驗證?這意味著只要知道了 ip 和端口就可以隨意拷貝服務(wù)器上的數(shù)據(jù)了?
那當(dāng)然不能夠了,我們可以通過在 主節(jié)點 配置 requirepass 來設(shè)置密碼,這樣就必須在 從節(jié)點 中對應(yīng)配置好 masterauth 參數(shù) (與主節(jié)點 requirepass 保持一致) 才能夠進行正常復(fù)制了。
SYNC 命令是一個非常耗費資源的操作
每次執(zhí)行 SYNC 命令,主從服務(wù)器需要執(zhí)行如下動作:
主服務(wù)器 需要執(zhí)行 BGSAVE 命令來生成 RDB 文件,這個生成操作會 消耗 主服務(wù)器大量的 CPU、內(nèi)存和磁盤 I/O 的資源;
主服務(wù)器 需要將自己生成的 RDB 文件 發(fā)送給從服務(wù)器,這個發(fā)送操作會 消耗 主服務(wù)器 大量的網(wǎng)絡(luò)資源 (帶寬和流量),并對主服務(wù)器響應(yīng)命令請求的時間產(chǎn)生影響;
接收到 RDB 文件的 從服務(wù)器 需要載入主服務(wù)器發(fā)來的 RBD 文件,并且在載入期間,從服務(wù)器 會因為阻塞而沒辦法處理命令請求;
特別是當(dāng)出現(xiàn) 斷線重復(fù)制 的情況是時,為了讓從服務(wù)器補足斷線時確實的那一小部分數(shù)據(jù),卻要執(zhí)行一次如此耗資源的 SYNC 命令,顯然是不合理的。
PSYNC 命令的引入
所以在 Redis 2.8 中引入了 PSYNC 命令來代替 SYNC,它具有兩種模式:
全量復(fù)制: 用于初次復(fù)制或其他無法進行部分復(fù)制的情況,將主節(jié)點中的所有數(shù)據(jù)都發(fā)送給從節(jié)點,是一個非常重型的操作;
部分復(fù)制: 用于網(wǎng)絡(luò)中斷等情況后的復(fù)制,只將 中斷期間主節(jié)點執(zhí)行的寫命令 發(fā)送給從節(jié)點,與全量復(fù)制相比更加高效。需要注意 的是,如果網(wǎng)絡(luò)中斷時間過長,導(dǎo)致主節(jié)點沒有能夠完整地保存中斷期間執(zhí)行的寫命令,則無法進行部分復(fù)制,仍使用全量復(fù)制;
部分復(fù)制的原理主要是靠主從節(jié)點分別維護一個 復(fù)制偏移量,有了這個偏移量之后斷線重連之后一比較,之后就可以僅僅把從服務(wù)器斷線之后確實的這部分數(shù)據(jù)給補回來了。
更多的詳細內(nèi)容可以參考下方 參考資料 3
三、Redis Sentinel 哨兵
上圖 展示了一個典型的哨兵架構(gòu)圖,它由兩部分組成,哨兵節(jié)點和數(shù)據(jù)節(jié)點:
哨兵節(jié)點: 哨兵系統(tǒng)由一個或多個哨兵節(jié)點組成,哨兵節(jié)點是特殊的 Redis 節(jié)點,不存儲數(shù)據(jù);
數(shù)據(jù)節(jié)點: 主節(jié)點和從節(jié)點都是數(shù)據(jù)節(jié)點;
在復(fù)制的基礎(chǔ)上,哨兵實現(xiàn)了 自動化的故障恢復(fù) 功能,下方是官方對于哨兵功能的描述:
監(jiān)控(Monitoring): 哨兵會不斷地檢查主節(jié)點和從節(jié)點是否運作正常。
自動故障轉(zhuǎn)移(Automatic failover): 當(dāng) 主節(jié)點 不能正常工作時,哨兵會開始 自動故障轉(zhuǎn)移操作,它會將失效主節(jié)點的其中一個 從節(jié)點升級為新的主節(jié)點,并讓其他從節(jié)點改為復(fù)制新的主節(jié)點。
配置提供者(Configuration provider): 客戶端在初始化時,通過連接哨兵來獲得當(dāng)前 Redis 服務(wù)的主節(jié)點地址。
通知(Notification): 哨兵可以將故障轉(zhuǎn)移的結(jié)果發(fā)送給客戶端。
其中,監(jiān)控和自動故障轉(zhuǎn)移功能,使得哨兵可以及時發(fā)現(xiàn)主節(jié)點故障并完成轉(zhuǎn)移。而配置提供者和通知功能,則需要在與客戶端的交互中才能體現(xiàn)。
■快速體驗
第一步:創(chuàng)建主從節(jié)點配置文件并啟動
正確安裝好 Redis 之后,我們?nèi)サ?Redis 的安裝目錄 (mac 默認在 /usr/local/),找到 redis.conf 文件復(fù)制三份分別命名為 redis-master.conf/redis-slave1.conf/redis-slave2.conf,分別作為 1 個主節(jié)點和 2 個從節(jié)點的配置文件 (下圖演示了我本機的 redis.conf 文件的位置)
打開可以看到這個 .conf 后綴的文件里面有很多說明的內(nèi)容,全部刪除然后分別改成下面的樣子:
#redis-master.conf port 6379 daemonize yes logfile "6379.log" dbfilename "dump-6379.rdb"#redis-slave1.conf port 6380 daemonize yes logfile "6380.log" dbfilename "dump-6380.rdb" slaveof 127.0.0.1 6379#redis-slave2.conf port 6381 daemonize yes logfile "6381.log" dbfilename "dump-6381.rdb" slaveof 127.0.0.1 6379然后我們可以執(zhí)行 redis-server <config file path> 來根據(jù)配置文件啟動不同的 Redis 實例,依次啟動主從節(jié)點:
redis-server /usr/local/redis-5.0.3/redis-master.conf redis-server /usr/local/redis-5.0.3/redis-slave1.conf redis-server /usr/local/redis-5.0.3/redis-slave2.conf節(jié)點啟動后,我們執(zhí)行 redis-cli 默認連接到我們端口為 6379 的主節(jié)點執(zhí)行 info Replication 檢查一下主從狀態(tài)是否正常:(可以看到下方正確地顯示了兩個從節(jié)點)
第二步:創(chuàng)建哨兵節(jié)點配置文件并啟動
按照上面同樣的方法,我們給哨兵節(jié)點也創(chuàng)建三個配置文件。(哨兵節(jié)點本質(zhì)上是特殊的 Redis 節(jié)點,所以配置幾乎沒什么差別,只是在端口上做區(qū)分就好)
# redis-sentinel-1.conf port 26379 daemonize yes logfile "26379.log" sentinel monitor mymaster 127.0.0.1 6379 2# redis-sentinel-2.conf port 26380 daemonize yes logfile "26380.log" sentinel monitor mymaster 127.0.0.1 6379 2# redis-sentinel-3.conf port 26381 daemonize yes logfile "26381.log" sentinel monitor mymaster 127.0.0.1 6379 2其中,sentinel monitor mymaster 127.0.0.1 6379 2 配置的含義是:該哨兵節(jié)點監(jiān)控 127.0.0.1:6379 這個主節(jié)點,該主節(jié)點的名稱是 mymaster,最后的 2 的含義與主節(jié)點的故障判定有關(guān):至少需要 2 個哨兵節(jié)點同意,才能判定主節(jié)點故障并進行故障轉(zhuǎn)移。
執(zhí)行下方命令將哨兵節(jié)點啟動起來:
redis-server /usr/local/redis-5.0.3/redis-sentinel-1.conf --sentinel redis-server /usr/local/redis-5.0.3/redis-sentinel-2.conf --sentinel redis-server /usr/local/redis-5.0.3/redis-sentinel-3.conf --sentinel使用 redis-cil 工具連接哨兵節(jié)點,并執(zhí)行 info Sentinel 命令來查看是否已經(jīng)在監(jiān)視主節(jié)點了:
# 連接端口為 26379 的 Redis 節(jié)點 ? ~ redis-cli -p 26379 127.0.0.1:26379> info Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3此時你打開剛才寫好的哨兵配置文件,你還會發(fā)現(xiàn)出現(xiàn)了一些變化:
第三步:演示故障轉(zhuǎn)移
首先,我們使用 kill -9 命令來殺掉主節(jié)點,同時 在哨兵節(jié)點中執(zhí)行 info Sentinel 命令來觀察故障節(jié)點的過程:
? ~ ps aux | grep 6379 longtao 74529 0.3 0.0 4346936 2132 ?? Ss 10:30上午 0:03.09 redis-server *:26379 [sentinel] longtao 73541 0.2 0.0 4348072 2292 ?? Ss 10:18上午 0:04.79 redis-server *:6379 longtao 75521 0.0 0.0 4286728 728 s008 S+ 10:39上午 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn 6379 longtao 74836 0.0 0.0 4289844 944 s006 S+ 10:32上午 0:00.01 redis-cli -p 26379 ? ~ kill -9 73541如果 剛殺掉瞬間 在哨兵節(jié)點中執(zhí)行 info 命令來查看,會發(fā)現(xiàn)主節(jié)點還沒有切換過來,因為哨兵發(fā)現(xiàn)主節(jié)點故障并轉(zhuǎn)移需要一段時間:
# 第一時間查看哨兵節(jié)點發(fā)現(xiàn)并未轉(zhuǎn)移,還在 6379 端口 127.0.0.1:26379> info Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3一段時間之后你再執(zhí)行 info 命令,查看,你就會發(fā)現(xiàn)主節(jié)點已經(jīng)切換成了 6381 端口的從節(jié)點:
# 過一段時間之后在執(zhí)行,發(fā)現(xiàn)已經(jīng)切換了 6381 端口 127.0.0.1:26379> info Sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3但同時還可以發(fā)現(xiàn),哨兵節(jié)點認為新的主節(jié)點仍然有兩個從節(jié)點 (上方 slaves=2),這是因為哨兵在將 6381 切換成主節(jié)點的同時,將 6379 節(jié)點置為其從節(jié)點。雖然 6379 從節(jié)點已經(jīng)掛掉,但是由于 哨兵并不會對從節(jié)點進行客觀下線,因此認為該從節(jié)點一直存在。當(dāng) 6379 節(jié)點重新啟動后,會自動變成 6381 節(jié)點的從節(jié)點。
另外,在故障轉(zhuǎn)移的階段,哨兵和主從節(jié)點的配置文件都會被改寫:
對于主從節(jié)點: 主要是 slaveof 配置的變化,新的主節(jié)點沒有了 slaveof 配置,其從節(jié)點則 slaveof 新的主節(jié)點。
對于哨兵節(jié)點: 除了主從節(jié)點信息的變化,紀元(epoch) (記錄當(dāng)前集群狀態(tài)的參數(shù)) 也會變化,紀元相關(guān)的參數(shù)都 +1 了。
■客戶端訪問哨兵系統(tǒng)代碼演示
上面我們在 快速體驗 中主要感受到了服務(wù)端自己對于當(dāng)前主從節(jié)點的自動化治理,下面我們以 Java 代碼為例,來演示一下客戶端如何訪問我們的哨兵系統(tǒng):
public static void testSentinel() throws Exception {String masterName = "mymaster";Set<String> sentinels = new HashSet<>();sentinels.add("127.0.0.1:26379");sentinels.add("127.0.0.1:26380");sentinels.add("127.0.0.1:26381");// 初始化過程做了很多工作JedisSentinelPool pool = new JedisSentinelPool(masterName, sentinels);Jedis jedis = pool.getResource();jedis.set("key1", "value1");pool.close(); }客戶端原理
Jedis 客戶端對哨兵提供了很好的支持。如上述代碼所示,我們只需要向 Jedis 提供哨兵節(jié)點集合和 masterName ,構(gòu)造 JedisSentinelPool 對象,然后便可以像使用普通 Redis 連接池一樣來使用了:通過 pool.getResource() 獲取連接,執(zhí)行具體的命令。
在整個過程中,我們的代碼不需要顯式的指定主節(jié)點的地址,就可以連接到主節(jié)點;代碼中對故障轉(zhuǎn)移沒有任何體現(xiàn),就可以在哨兵完成故障轉(zhuǎn)移后自動的切換主節(jié)點。之所以可以做到這一點,是因為在 JedisSentinelPool 的構(gòu)造器中,進行了相關(guān)的工作;主要包括以下兩點:
遍歷哨兵節(jié)點,獲取主節(jié)點信息: 遍歷哨兵節(jié)點,通過其中一個哨兵節(jié)點 + masterName 獲得主節(jié)點的信息;該功能是通過調(diào)用哨兵節(jié)點的 sentinel get-master-addr-by-name 命令實現(xiàn);
增加對哨兵的監(jiān)聽: 這樣當(dāng)發(fā)生故障轉(zhuǎn)移時,客戶端便可以收到哨兵的通知,從而完成主節(jié)點的切換。具體做法是:利用 Redis 提供的 發(fā)布訂閱 功能,為每一個哨兵節(jié)點開啟一個單獨的線程,訂閱哨兵節(jié)點的 + switch-master 頻道,當(dāng)收到消息時,重新初始化連接池。
■新的主服務(wù)器是怎樣被挑選出來的?
故障轉(zhuǎn)移操作的第一步 要做的就是在已下線主服務(wù)器屬下的所有從服務(wù)器中,挑選出一個狀態(tài)良好、數(shù)據(jù)完整的從服務(wù)器,然后向這個從服務(wù)器發(fā)送 slaveof no one 命令,將這個從服務(wù)器轉(zhuǎn)換為主服務(wù)器。但是這個從服務(wù)器是怎么樣被挑選出來的呢?
簡單來說 Sentinel 使用以下規(guī)則來選擇新的主服務(wù)器:
在失效主服務(wù)器屬下的從服務(wù)器當(dāng)中, 那些被標記為主觀下線、已斷線、或者最后一次回復(fù) PING 命令的時間大于五秒鐘的從服務(wù)器都會被 淘汰。
在失效主服務(wù)器屬下的從服務(wù)器當(dāng)中, 那些與失效主服務(wù)器連接斷開的時長超過 down-after 選項指定的時長十倍的從服務(wù)器都會被 淘汰。
在 經(jīng)歷了以上兩輪淘汰之后 剩下來的從服務(wù)器中, 我們選出 復(fù)制偏移量(replication offset)最大 的那個 從服務(wù)器 作為新的主服務(wù)器;如果復(fù)制偏移量不可用,或者從服務(wù)器的復(fù)制偏移量相同,那么 帶有最小運行 ID 的那個從服務(wù)器成為新的主服務(wù)器。
四、Redis 集群
上圖 展示了 Redis Cluster 典型的架構(gòu)圖,集群中的每一個 Redis 節(jié)點都 互相兩兩相連,客戶端任意 直連 到集群中的 任意一臺,就可以對其他 Redis 節(jié)點進行 讀寫 的操作。
基本原理
Redis 集群中內(nèi)置了 16384 個哈希槽。當(dāng)客戶端連接到 Redis 集群之后,會同時得到一份關(guān)于這個 集群的配置信息,當(dāng)客戶端具體對某一個 key 值進行操作時,會計算出它的一個 Hash 值,然后把結(jié)果對 16384 ?求余數(shù),這樣每個 key 都會對應(yīng)一個編號在 0-16383 之間的哈希槽,Redis 會根據(jù)節(jié)點數(shù)量 大致均等 的將哈希槽映射到不同的節(jié)點。
再結(jié)合集群的配置信息就能夠知道這個 key 值應(yīng)該存儲在哪一個具體的 Redis 節(jié)點中,如果不屬于自己管,那么就會使用一個特殊的 MOVED 命令來進行一個跳轉(zhuǎn),告訴客戶端去連接這個節(jié)點以獲取數(shù)據(jù):
GET x -MOVED 3999 127.0.0.1:6381MOVED 指令第一個參數(shù) 3999 是 key 對應(yīng)的槽位編號,后面是目標節(jié)點地址,MOVED 命令前面有一個減號,表示這是一個錯誤的消息。客戶端在收到 MOVED 指令后,就立即糾正本地的 槽位映射表,那么下一次再訪問 key 時就能夠到正確的地方去獲取了。
集群的主要作用
數(shù)據(jù)分區(qū): 數(shù)據(jù)分區(qū) (或稱數(shù)據(jù)分片) 是集群最核心的功能。集群將數(shù)據(jù)分散到多個節(jié)點,一方面 突破了 Redis 單機內(nèi)存大小的限制,存儲容量大大增加;另一方面 每個主節(jié)點都可以對外提供讀服務(wù)和寫服務(wù),大大提高了集群的響應(yīng)能力。Redis 單機內(nèi)存大小受限問題,在介紹持久化和主從復(fù)制時都有提及,例如,如果單機內(nèi)存太大,bgsave 和 bgrewriteaof 的 fork 操作可能導(dǎo)致主進程阻塞,主從環(huán)境下主機切換時可能導(dǎo)致從節(jié)點長時間無法提供服務(wù),全量復(fù)制階段主節(jié)點的復(fù)制緩沖區(qū)可能溢出……
高可用: 集群支持主從復(fù)制和主節(jié)點的 自動故障轉(zhuǎn)移 (與哨兵類似),當(dāng)任一節(jié)點發(fā)生故障時,集群仍然可以對外提供服務(wù)。
■快速體驗
第一步:創(chuàng)建集群節(jié)點配置文件
首先我們找一個地方創(chuàng)建一個名為 redis-cluster 的目錄:
mkdir -p ~/Desktop/redis-cluster然后按照上面的方法,創(chuàng)建六個配置文件,分別命名為:redis_7000.conf/redis_7001.conf.....redis_7005.conf,然后根據(jù)不同的端口號修改對應(yīng)的端口值就好了:
# 后臺執(zhí)行 daemonize yes # 端口號 port 7000 # 為每一個集群節(jié)點指定一個 pid_file pidfile ~/Desktop/redis-cluster/redis_7000.pid # 啟動集群模式 cluster-enabled yes # 每一個集群節(jié)點都有一個配置文件,這個文件是不能手動編輯的。確保每一個集群節(jié)點的配置文件不通 cluster-config-file nodes-7000.conf # 集群節(jié)點的超時時間,單位:ms,超時后集群會認為該節(jié)點失敗 cluster-node-timeout 5000 # 最后將 appendonly 改成 yes(AOF 持久化) appendonly yes記得把對應(yīng)上述配置文件中根端口對應(yīng)的配置都修改掉 (port/ pidfile/ cluster-config-file)。
第二步:分別啟動 6 個 Redis 實例
redis-server ~/Desktop/redis-cluster/redis_7000.conf redis-server ~/Desktop/redis-cluster/redis_7001.conf redis-server ~/Desktop/redis-cluster/redis_7002.conf redis-server ~/Desktop/redis-cluster/redis_7003.conf redis-server ~/Desktop/redis-cluster/redis_7004.conf redis-server ~/Desktop/redis-cluster/redis_7005.conf然后執(zhí)行 ps -ef | grep redis 查看是否啟動成功:
可以看到 6 個 Redis 節(jié)點都以集群的方式成功啟動了,但是現(xiàn)在每個節(jié)點還處于獨立的狀態(tài),也就是說它們每一個都各自成了一個集群,還沒有互相聯(lián)系起來,我們需要手動地把他們之間建立起聯(lián)系。
第三步:建立集群
執(zhí)行下列命令:
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005這里稍微解釋一下這個 --replicas 1 的意思是:我們希望為集群中的每個主節(jié)點創(chuàng)建一個從節(jié)點。
觀察控制臺輸出:
看到 [OK] 的信息之后,就表示集群已經(jīng)搭建成功了,可以看到,這里我們正確地創(chuàng)建了三主三從的集群。
第四步:驗證集群
我們先使用 redic-cli 任意連接一個節(jié)點:
redis-cli -c -h 127.0.0.1 -p 7000 127.0.0.1:7000>-c表示集群模式;-h 指定 ip 地址;-p 指定端口。
然后隨便 set 一些值觀察控制臺輸入:
127.0.0.1:7000> SET name wmyskxz -> Redirected to slot [5798] located at 127.0.0.1:7001 OK 127.0.0.1:7001>可以看到這里 Redis 自動幫我們進行了 Redirected 操作跳轉(zhuǎn)到了 7001 這個實例上。
我們再使用 cluster info (查看集群信息) 和 cluster nodes (查看節(jié)點列表) 來分別看看:(任意節(jié)點輸入均可)
127.0.0.1:7001> CLUSTER INFO cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:2 cluster_stats_messages_ping_sent:1365 cluster_stats_messages_pong_sent:1358 cluster_stats_messages_meet_sent:4 cluster_stats_messages_sent:2727 cluster_stats_messages_ping_received:1357 cluster_stats_messages_pong_received:1369 cluster_stats_messages_meet_received:1 cluster_stats_messages_received:2727127.0.0.1:7001> CLUSTER NODES 56a04742f36c6e84968cae871cd438935081e86f 127.0.0.1:7003@17003 slave 4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 0 1584428884000 4 connected 4ec8c022e9d546c9b51deb9d85f6cf867bf73db6 127.0.0.1:7000@17000 master - 0 1584428884000 1 connected 0-5460 e2539c4398b8258d3f9ffa714bd778da107cb2cd 127.0.0.1:7005@17005 slave a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 0 1584428885222 6 connected d31cd1f423ab1e1849cac01ae927e4b6950f55d9 127.0.0.1:7004@17004 slave 236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 0 1584428884209 5 connected 236cefaa9cdc295bc60a5bd1aed6a7152d4f384d 127.0.0.1:7001@17001 myself,master - 0 1584428882000 2 connected 5461-10922 a3406db9ae7144d17eb7df5bffe8b70bb5dd06b8 127.0.0.1:7002@17002 master - 0 1584428884000 3 connected 10923-16383 127.0.0.1:7001>■數(shù)據(jù)分區(qū)方案簡析
方案一:哈希值 % 節(jié)點數(shù)
哈希取余分區(qū)思路非常簡單:計算 key 的 hash 值,然后對節(jié)點數(shù)量進行取余,從而決定數(shù)據(jù)映射到哪個節(jié)點上。
不過該方案最大的問題是,當(dāng)新增或刪減節(jié)點時,節(jié)點數(shù)量發(fā)生變化,系統(tǒng)中所有的數(shù)據(jù)都需要 重新計算映射關(guān)系,引發(fā)大規(guī)模數(shù)據(jù)遷移。
方案二:一致性哈希分區(qū)
一致性哈希算法將 整個哈希值空間 組織成一個虛擬的圓環(huán),范圍是 [0 - 232 - 1],對于每一個數(shù)據(jù),根據(jù) key 計算 hash 值,確數(shù)據(jù)在環(huán)上的位置,然后從此位置沿順時針行走,找到的第一臺服務(wù)器就是其應(yīng)該映射到的服務(wù)器:
與哈希取余分區(qū)相比,一致性哈希分區(qū)將 增減節(jié)點的影響限制在相鄰節(jié)點。以上圖為例,如果在 node1 和 node2 之間增加 node5,則只有 node2 中的一部分數(shù)據(jù)會遷移到 node5;如果去掉 node2,則原 node2 中的數(shù)據(jù)只會遷移到 node4 中,只有 node4 會受影響。
一致性哈希分區(qū)的主要問題在于,當(dāng) 節(jié)點數(shù)量較少 時,增加或刪減節(jié)點,對單個節(jié)點的影響可能很大,造成數(shù)據(jù)的嚴重不平衡。還是以上圖為例,如果去掉 node2,node4 中的數(shù)據(jù)由總數(shù)據(jù)的 1/4 左右變?yōu)?1/2 左右,與其他節(jié)點相比負載過高。
方案三:帶有虛擬節(jié)點的一致性哈希分區(qū)
該方案在 一致性哈希分區(qū)的基礎(chǔ)上,引入了 虛擬節(jié)點 的概念。Redis 集群使用的便是該方案,其中的虛擬節(jié)點稱為 槽(slot)。槽是介于數(shù)據(jù)和實際節(jié)點之間的虛擬概念,每個實際節(jié)點包含一定數(shù)量的槽,每個槽包含哈希值在一定范圍內(nèi)的數(shù)據(jù)。
在使用了槽的一致性哈希分區(qū)中,槽是數(shù)據(jù)管理和遷移的基本單位。槽 解耦 了 數(shù)據(jù)和實際節(jié)點 之間的關(guān)系,增加或刪除節(jié)點對系統(tǒng)的影響很小。仍以上圖為例,系統(tǒng)中有 4 個實際節(jié)點,假設(shè)為其分配 16 個槽(0-15);
槽 0-3 位于 node1;4-7 位于 node2;以此類推....
如果此時刪除 node2,只需要將槽 4-7 重新分配即可,例如槽 4-5 分配給 node1,槽 6 分配給 node3,槽 7 分配給 node4;可以看出刪除 node2 后,數(shù)據(jù)在其他節(jié)點的分布仍然較為均衡。
■節(jié)點通信機制簡析
集群的建立離不開節(jié)點之間的通信,例如我們上訪在 快速體驗 中剛啟動六個集群節(jié)點之后通過 redis-cli 命令幫助我們搭建起來了集群,實際上背后每個集群之間的兩兩連接是通過了 CLUSTER MEET <ip> <port> 命令發(fā)送 MEET 消息完成的,下面我們展開詳細說說。
兩個端口
在 哨兵系統(tǒng) 中,節(jié)點分為 數(shù)據(jù)節(jié)點 和 哨兵節(jié)點:前者存儲數(shù)據(jù),后者實現(xiàn)額外的控制功能。在 集群 中,沒有數(shù)據(jù)節(jié)點與非數(shù)據(jù)節(jié)點之分:所有的節(jié)點都存儲數(shù)據(jù),也都參與集群狀態(tài)的維護。為此,集群中的每個節(jié)點,都提供了兩個 TCP 端口:
普通端口: 即我們在前面指定的端口 (7000等)。普通端口主要用于為客戶端提供服務(wù) (與單機節(jié)點類似);但在節(jié)點間數(shù)據(jù)遷移時也會使用。
集群端口: 端口號是普通端口 + 10000 (10000是固定值,無法改變),如 7000 節(jié)點的集群端口為 17000。集群端口只用于節(jié)點之間的通信,如搭建集群、增減節(jié)點、故障轉(zhuǎn)移等操作時節(jié)點間的通信;不要使用客戶端連接集群接口。為了保證集群可以正常工作,在配置防火墻時,要同時開啟普通端口和集群端口。
Gossip 協(xié)議
節(jié)點間通信,按照通信協(xié)議可以分為幾種類型:單對單、廣播、Gossip 協(xié)議等。重點是廣播和 Gossip 的對比。
廣播是指向集群內(nèi)所有節(jié)點發(fā)送消息。優(yōu)點 是集群的收斂速度快(集群收斂是指集群內(nèi)所有節(jié)點獲得的集群信息是一致的),缺點 是每條消息都要發(fā)送給所有節(jié)點,CPU、帶寬等消耗較大。
Gossip 協(xié)議的特點是:在節(jié)點數(shù)量有限的網(wǎng)絡(luò)中,每個節(jié)點都 “隨機” 的與部分節(jié)點通信 (并不是真正的隨機,而是根據(jù)特定的規(guī)則選擇通信的節(jié)點),經(jīng)過一番雜亂無章的通信,每個節(jié)點的狀態(tài)很快會達到一致。Gossip 協(xié)議的 優(yōu)點 有負載 (比廣播) 低、去中心化、容錯性高 (因為通信有冗余) 等;缺點 主要是集群的收斂速度慢。
消息類型
集群中的節(jié)點采用 固定頻率(每秒10次) 的 定時任務(wù) 進行通信相關(guān)的工作:判斷是否需要發(fā)送消息及消息類型、確定接收節(jié)點、發(fā)送消息等。如果集群狀態(tài)發(fā)生了變化,如增減節(jié)點、槽狀態(tài)變更,通過節(jié)點間的通信,所有節(jié)點會很快得知整個集群的狀態(tài),使集群收斂。
節(jié)點間發(fā)送的消息主要分為 5 種:meet 消息、ping 消息、pong 消息、fail 消息、publish 消息。不同的消息類型,通信協(xié)議、發(fā)送的頻率和時機、接收節(jié)點的選擇等是不同的:
MEET 消息: 在節(jié)點握手階段,當(dāng)節(jié)點收到客戶端的 CLUSTER MEET 命令時,會向新加入的節(jié)點發(fā)送 MEET 消息,請求新節(jié)點加入到當(dāng)前集群;新節(jié)點收到 MEET 消息后會回復(fù)一個 PONG 消息。
PING 消息: 集群里每個節(jié)點每秒鐘會選擇部分節(jié)點發(fā)送 PING 消息,接收者收到消息后會回復(fù)一個 PONG 消息。PING 消息的內(nèi)容是自身節(jié)點和部分其他節(jié)點的狀態(tài)信息,作用是彼此交換信息,以及檢測節(jié)點是否在線。PING 消息使用 Gossip 協(xié)議發(fā)送,接收節(jié)點的選擇兼顧了收斂速度和帶寬成本,具體規(guī)則如下:(1)隨機找 5 個節(jié)點,在其中選擇最久沒有通信的 1 個節(jié)點;(2)掃描節(jié)點列表,選擇最近一次收到 PONG 消息時間大于 cluster_node_timeout / 2 的所有節(jié)點,防止這些節(jié)點長時間未更新。
PONG消息: PONG 消息封裝了自身狀態(tài)數(shù)據(jù)。可以分為兩種:第一種 是在接到 MEET/PING 消息后回復(fù)的 PONG 消息;第二種 是指節(jié)點向集群廣播 PONG 消息,這樣其他節(jié)點可以獲知該節(jié)點的最新信息,例如故障恢復(fù)后新的主節(jié)點會廣播 PONG 消息。
FAIL 消息: 當(dāng)一個主節(jié)點判斷另一個主節(jié)點進入 FAIL 狀態(tài)時,會向集群廣播這一 FAIL 消息;接收節(jié)點會將這一 FAIL 消息保存起來,便于后續(xù)的判斷。
PUBLISH 消息: 節(jié)點收到 PUBLISH 命令后,會先執(zhí)行該命令,然后向集群廣播這一消息,接收節(jié)點也會執(zhí)行該 PUBLISH 命令。
■數(shù)據(jù)結(jié)構(gòu)簡析
節(jié)點需要專門的數(shù)據(jù)結(jié)構(gòu)來存儲集群的狀態(tài)。所謂集群的狀態(tài),是一個比較大的概念,包括:集群是否處于上線狀態(tài)、集群中有哪些節(jié)點、節(jié)點是否可達、節(jié)點的主從狀態(tài)、槽的分布……
節(jié)點為了存儲集群狀態(tài)而提供的數(shù)據(jù)結(jié)構(gòu)中,最關(guān)鍵的是 clusterNode 和 clusterState 結(jié)構(gòu):前者記錄了一個節(jié)點的狀態(tài),后者記錄了集群作為一個整體的狀態(tài)。
clusterNode 結(jié)構(gòu)
clusterNode 結(jié)構(gòu)保存了 一個節(jié)點的當(dāng)前狀態(tài),包括創(chuàng)建時間、節(jié)點 id、ip 和端口號等。每個節(jié)點都會用一個 clusterNode 結(jié)構(gòu)記錄自己的狀態(tài),并為集群內(nèi)所有其他節(jié)點都創(chuàng)建一個 clusterNode 結(jié)構(gòu)來記錄節(jié)點狀態(tài)。
下面列舉了 clusterNode 的部分字段,并說明了字段的含義和作用:
typedef struct clusterNode {//節(jié)點創(chuàng)建時間mstime_t ctime;//節(jié)點idchar name[REDIS_CLUSTER_NAMELEN];//節(jié)點的ip和端口號char ip[REDIS_IP_STR_LEN];int port;//節(jié)點標識:整型,每個bit都代表了不同狀態(tài),如節(jié)點的主從狀態(tài)、是否在線、是否在握手等int flags;//配置紀元:故障轉(zhuǎn)移時起作用,類似于哨兵的配置紀元uint64_t configEpoch;//槽在該節(jié)點中的分布:占用16384/8個字節(jié),16384個比特;每個比特對應(yīng)一個槽:比特值為1,則該比特對應(yīng)的槽在節(jié)點中;比特值為0,則該比特對應(yīng)的槽不在節(jié)點中unsigned char slots[16384/8];//節(jié)點中槽的數(shù)量int numslots;………… } clusterNode;除了上述字段,clusterNode 還包含節(jié)點連接、主從復(fù)制、故障發(fā)現(xiàn)和轉(zhuǎn)移需要的信息等。
clusterState 結(jié)構(gòu)
clusterState 結(jié)構(gòu)保存了在當(dāng)前節(jié)點視角下,集群所處的狀態(tài)。主要字段包括:
typedef struct clusterState {//自身節(jié)點clusterNode *myself;//配置紀元uint64_t currentEpoch;//集群狀態(tài):在線還是下線int state;//集群中至少包含一個槽的節(jié)點數(shù)量int size;//哈希表,節(jié)點名稱->clusterNode節(jié)點指針dict *nodes;//槽分布信息:數(shù)組的每個元素都是一個指向clusterNode結(jié)構(gòu)的指針;如果槽還沒有分配給任何節(jié)點,則為NULLclusterNode *slots[16384];………… } clusterState;除此之外,clusterState 還包括故障轉(zhuǎn)移、槽遷移等需要的信息。
更多關(guān)于集群內(nèi)容請自行閱讀《Redis 設(shè)計與實現(xiàn)》,其中有更多細節(jié)方面的介紹 - http://redisbook.com/參考資料
《Redis 設(shè)計與實現(xiàn)》 | 黃健宏 著 - http://redisbook.com/
《Redis 深度歷險》 | 錢文品 著 - https://book.douban.com/subject/30386804/
深入學(xué)習(xí)Redis(3):主從復(fù)制 - https://www.cnblogs.com/kismetv/p/9236731.html
Redis 主從復(fù)制 原理與用法 - https://blog.csdn.net/Stubborn_Cow/article/details/50442950
深入學(xué)習(xí)Redis(4):哨兵 - https://www.cnblogs.com/kismetv/p/9609938.html
Redis 5 之后版本的高可用集群搭建 - https://www.jianshu.com/p/8045b92fafb2
本文已收錄至Github 程序員成長系列 【More Than Java】,學(xué)習(xí),不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
END
Java面試題專欄
【51期】一道阿里面試題:說說你知道的關(guān)于BeanFactory和FactoryBean的區(qū)別
【52期】記一道簡單的Java面試題,但答錯率很高!
【53期】面試官:談一下數(shù)據(jù)庫分庫分表之后,你是如何解決事務(wù)問題?
【54期】Java序列化三連問,是什么?為什么需要?如何實現(xiàn)?
【55期】面試中經(jīng)常被問到Java引用類型原理,帶你深入剖析
【56期】你說你熟悉并發(fā)編程,那么你說說Java鎖有哪些種類,以及區(qū)別
【57期】面試官問,MySQL建索引需要遵循哪些原則呢?
【58期】盤點那些面試中最常問的MySQL問題,第一彈!
【59期】MySQL索引是如何提高查詢效率的呢?(MySQL面試第二彈)
【60期】事務(wù)隔離級別中的可重復(fù)讀能防幻讀嗎?(MySQL面試第三彈)
歡迎長按下圖關(guān)注公眾號后端技術(shù)精選
總結(jié)
以上是生活随笔為你收集整理的Redis:史上最强【集群】入门实践教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 系统测试计划
- 下一篇: 魔兽单机80mysql创建账号_本站魔兽