Redis 集羣原理,再也不怕面試被問倒

圖片來自 Pexels

本文主要圍繞如下幾個方面介紹集羣:

  • 集羣簡介

  • 集羣作用

  • 配置集羣

  • 手動、自動故障轉移

  • 故障轉移原理

本文實現環境:

  • CentOS 7.3

  • Redis 4.0

  • Redis 工作目錄 /usr/local/redis

  • 所有操作均在虛擬機模擬進行


集羣簡介

集羣是爲了解決主從複製中單機內存上限和併發問題,假如你現在的雲服務內存爲 256GB,當達到這個內存時 Redis 就沒辦法再提供服務。

同時數據量能達到這個地步寫數據量也會很大,容易造成緩衝區溢出,造成從節點無限的進行全量複製導致主從無法正常工作。

那麼我們就需要把單機的主從改爲多對多的方式,並且所有的主節點都會連接在一起互相通信。

這樣的方式既可以分擔單機內存,也可以分發請求,提高系統的可用性。

如下圖:當有大量請求寫入時,不再會單一的向一個主節點發送指令,而會把指令進行分流到各個主節點,達到分擔內存、避免大量請求的作用。

那麼指令是如何進行分流存儲的呢?我們就需要到集羣存儲結構中一探究竟。

集羣作用

集羣的作用有如下幾個:

  • 分散單機的存儲能力,同時也可以很方便的實現擴展。

  • 分流單機的訪問請求。

  • 提高系統的可用性。

如何理解提高系統的可用性這句話,我們看下圖,當 master1 宕機後對系統的影響不會那麼大,仍然可以提供正常的服務。

這個時候就會有人問了,當 master1 宕機後,集羣這個時候怎麼工作呀?這個問題會在下文的故障轉移來給你解答,並且在原理篇會對這個問題進行詳解。

集羣存儲結構

存儲結構

單機的存儲是當用戶發起請求後直接把 key 存儲到自己的內存即可。 

集羣的存儲結構就沒有那麼簡單了,首先當用戶發起一個 key 指令後需要做的事情如下:

  • 通過 CRC16(key) 會計算出來一個值。

  • 用這個值取模 16384,會得到一個值,我們就先認爲是 28。

  • 這個值 28 就是 key 保存的空間位置。

那麼現在問題來了,這個 key 到底應該存儲在哪個 Redis 存儲空間裏邊呢?

其實 Redis 在集羣啓動後就已經把存儲空間劃分了 16384 份,每臺主機保存一部分。

這裏需要注意的是我給每個 Redis 存儲空間裏邊的編號就相當於一個小的存儲空間(專業術語“哈希槽”)。

你可以理解爲一棟樓裏邊的編號,一棟樓就是 Redis 的整個存儲空間,每個房子的編號就相當於一個存儲空間,這個存儲空間會有一定的區域來保存對應的 key,並非上圖取模後的位置。

箭頭指向的 28 是指的 28 會存儲在這個區域裏,這個房子有可能會存儲 29、30、31 等。

此時問題來了,如果新增、減少一臺機器後怎麼辦呢?看圖說話,能用圖說明儘量不去用文字。

在新增一臺機器後,會從其他三個存儲空間中拿出一定的槽分配給新的機器。這裏可以自己設置想給新的機器放多少個槽。

同樣減少一臺機器後會把去掉的槽在重新分配給其它現有的機器跟新增節點一樣,可以指定節點接收槽。

所謂的增節點或去節點就是改變槽所存儲的位置不同。

瞭解了集羣的存儲結構後,我們就需要在對另一個問題進行說明了,集羣是如何設計內部通訊呢?

來了一個值,獲取一個 key,去哪拿數據?跟着這個問題我們看下文。

通訊設計

集羣中的每個節點會在一定的時期給其它節點發送 ping 消息,其他節點返回 pong 作爲響應。

經過一段時間後所有節點都會知道集羣全部節點的槽信息。如下圖有三個節點,那麼就會把 16384 個哈希槽分成三份。

分別爲:

  • 0-5500

  • 5501-11000

  • 11001-16384

當用戶發起了一個 key 的請求,集羣是如何處理請求的呢?上圖的黑框代表這集羣所有節點的槽信息,裏邊還有很多其它信息。 

如圖所示,用戶發起請求 key,Redis 接收後計算 key 的槽位置,在根據槽位置找出對應的節點。

如果訪問的槽就在節點本身,那麼就會直接返回 key 對應數據。否則會回覆 moved 重定向錯誤,並且給客戶端返回正確的節點。

然後重發 key 指令,如下圖:

配置集羣

①修改配置文件


如下圖:

只需要注意圈中的配置信息即可:

  • cluster-enabled yes:開啓集羣模式。

  • cluster-config-file nodes-6379.conf:集羣配置文件。

  • clustre-node-timeout 10000:節點超時時間,這裏爲了方便測試設置爲 10s。

②構建 6 個節點的配置文件並全啓動

給大家提供一個命令可以很方便的替換文件:

sed 's/6379/6380/g' 6379-redis.conf > 6380-redis.conf

按照這樣的方式創建出來 6 個不同端口的配置文件:

隨便打開一個配置文件查看,檢測是否替換成功:

爲了查看日誌信息方便,全部使用前臺啓動。並且查看服務是否都正常啓動,執行命令:

ps -ef | grep redis

可以看到啓動後多了個 cluster 標識,代表着都是集羣的一個節點。

所有節點啓動完成,集羣啓動的指令需要基於 Ruby(本人使用 Redis 版本爲 4.0),接下來一起安裝。

③安裝 Ruby

執行命令:

wget https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.gz

解壓(根據自己下載的版本來解壓):

tar -xvzf ruby-2.7.1.tar.gz

安裝:

./configure | make | make install

這三個指令一氣呵成,查看 ruby 和 gem 版本:ruby -v。

④啓動集羣

集羣的執行命令在 /usr/local/redis/src/redis-trib.rb,注意如果需要直接使用 redis-trib.rb 命令,需要 ln 到 bin 目錄下,否則就必須使用 ./redis-trib.rb 的方式。

如果按照步驟走,這裏會出現一個錯誤,如下圖:

執行 gem install redis,很不幸的是在這裏也會出現錯誤:

隨後需要安裝 yum install zlib-devel 和 yum install openssl-devel。

安裝完成後,在 /ruby-2.7.1/ext/openssl 和 /ruby-2.7.1/ext/zlib 分別執行 ruby extconf.rb,並且執行 make | make install。

然後再執行 gem install redis 就 OK:

這時再回頭來執行:

./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384

信息解讀:創建集羣,並且給 6 個節點分配哈希槽,後三個節點配置爲前三個節點的從節點。

顯示每個節點的哈希槽信息和節點 ID,最後一步需要輸入 yes:

來到 data 目錄下查看配置文件的變化。配置文件主要信息是每個主節點分的槽:

查看主機點的運行日誌:這裏給的主要信息 cluster status changed:ok 集羣狀態正常。

⑤集羣設置與獲取數據

當直接設置數據會報錯,並且把 name 這個 key 進行轉化後的槽位置爲 5798 並且給出了 ip 地址和端口號。 

需要使用命令 redis-cli -c,在進行設置值的時候提示說重定向到 5798 的這個槽。

接下來進行獲取數據,會自動的切換節點:

故障轉移

①集羣從節點下線

根據上文集羣啓動信息知道端口 6383 是 6379 的從節點。接下來就是讓 6383 下線查看 6379 的日誌信息。

6379 會報出連接 6383 丟失,並且給上標記 fail,表示不可用。這個時候集羣還是正常工作的。

總結:從節點下線對集羣沒有影響。

當端口 6383 上線後,所有的節點會把 fail 的標記清除,如下圖:

②集羣主節點下線

手動下線主節點 6379,查看從節點 6383 日誌信息,此時的 6383 節點會持續連接 6379 共計 10 次,那爲什麼是 10 次呢?

是根據我們配置的參數 cluster-node-timeout 10 來決定的,這裏給我們一個信息就是一秒連接一次。

直到時間到期後,開始故障轉移。這時 6383 在故障轉移選舉中勝任,翻身奴隸把歌唱,成爲了主節點。

此時在查看一下集羣的節點信息,命令 cluster nodes。會發現這裏竟然存在四個主節點,但是其中一個主節點時下線狀態:

6379 原主節點上線:6379 上線後,同樣所有的節點也會清除 fail 信息。並且節點信息也會改變,此時的 6379 改變爲 6383 的從節點。

③新增主節點

再新增倆個端口 6385 和 6386:

執行新增命令 ./redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379,這裏發送的就是 meet 消息。

執行 add-node 命令,第一個參數爲新節點的 ip+端口,第二個參數爲已存在集羣中的節點。根據下圖我們就可以看到新增的節點已經存在集羣中了。

注意:雖說 6385 已經成爲集羣中的節點了,但是跟其它節點有區別。它沒有數據,也就是沒有哈希槽。

接下來我們就需要把集羣中的某些哈希槽分配到這個新節點上,分配結束後這個節點纔會成爲真正意義上的主節點。

執行命令 ./redis-trib.rb reshard 127.0.0.1:6385,會提示轉移多少個哈希槽並填寫接收節點的 id。

最後一步詢問是否從所有節點中轉移:我使用的是 all。使用指令:cluster nodes 查看,6385 的這個節點就已經擁有三個範圍的哈希槽了。

主節點已經新增好了,接下來就需要給 6385 這個主節點配置一個從節點 6386,命令如下:

./redis-trib.rb add-node --slave --master-id dcc0ec4d0c932ac5c35ae76af4f9c5d27a422d9f 127.0.0.1:6386 127.0.0.1:6385

master-id 是 6385 的 id,第一個參數爲新節點的 ip+端口,第二個爲指定的主節點 ip+端口。

④手動故障遷移

當想對集羣中的主節點進行升級的話可以手動執行故障轉移到從節點,避免集羣可用性受影響。

在從節點執行命令:cluster failover。

執行過程:查看節點信息就可以看到 6386 這個節點已經成爲了主機點。

當給從節點發送 cluster failover 指令後,從節點會給主節點發送 CLUSTERMSG_TYPE_MFSTART 包。從節點請求主節點停止訪問,從而對比兩者的數據偏移量達到一致。

這時客戶端不會連接我們淘汰的主節點,同時主節點向從節點發送複製偏移量,從節點得到複製偏移量後故障轉移開始,接着通知主節點進行配置切換。

當客戶端在舊的 master 上解鎖後,重新連接到新的主節點上。 

故障轉移原理篇

上文中我們測試了故障轉移,主節點下線後從節點變爲主節點,接下來剖析這個過程。

①故障發現到確認

集羣中的每個節點會定期的給其它節點發送 ping 消息,接收方用 pong 作爲回覆。

如果在 cluster-node-timeout 的時間內 ping 消息一直失敗,則會把接收方的節點標記爲 pfail 狀態也就是主觀下線。

這個下線狀態是不是很熟悉?沒錯,這個跟哨兵判斷主節點是否異常有點相似。

當一個哨兵發現主節點有問題時也會標記主節點客觀下線(s_down)。突然發現跑題了,尷尬......

再提一下哨兵,當一個哨兵認爲主節點異常後標記主觀下線,但是其他哨兵怎麼能會同意,不能你說什麼就是什麼。

都會去嘗試連接異常的主節點,當半數以上的哨兵都認爲主節點異常後會直接讓其主節點客觀下線。

同樣集羣也不會因爲一個節點判斷其狀態爲下線就行的,節點直接通過 Gossip 消息傳播,集羣中節點會不斷收集故障節點的下線反饋並且存儲到本地的故障節點下線報告中。

當有半數以上的集羣主節點都標記爲主觀下線後改變狀態爲客觀下線。最後向集羣廣播一條 fail 消息,通知所有節點將故障節點標記爲客觀下線。

例如:節點 A 發送 ping 到節點 B 通信異常後標記節點 B 爲 pfail,之後節點 A 會繼續給節點 C 發送 ping,並且攜帶節點 B 的 pfail 信息,然後節點 C 將節點 B 的故障保存到下線報告中。

當下線報告數量大於有哈希槽主節點的一半數量以上後就會嘗試客觀下線。

②故障恢復(從節點從此翻身奴隸把歌唱)

當故障節點被定義爲客觀下線後,故障節點的所有從節點承擔故障恢復的責任。

故障恢復是從節點通過定時任務發現自己的主機點客觀下線後就會執行故障恢復流程。

資格檢查:所有的從節點都會進行檢查與主節點最後的連接時間,斷線時間大於 cluster-node-time*cluster-slave-validity-factor 時不具備故障轉移的資格。

準備選舉時間:先說說爲什麼這裏會有一個準備選舉時間。資格檢查過後存在多個從節點,那麼就需要使用不同的延遲選舉時間來支持優先級。

這裏的優先級就是以複製偏移量爲基準,偏移量越大與故障主節點的延遲越小,那麼就更有機會擁有替換主節點的機會。主要的作用就是確保數據一致性最好的節點優先發起選舉。

選舉投票:Redis 集羣的投票機制沒有采用從節點進行領導選舉,這點切記不要跟哨兵搞混了。集羣的投票機制都是持有槽的主機點進行投票的。

故障節點的從節點會廣播一個 FAILOVER_AUTH_REQUEST 數據包給所有的持有槽的主節點請求投票。

當主節點回復 FAILOVER_AUTH_ACK 投票後在 NODE_TIMEOUT * 2 的這段時間不能給其他的從節點投票。從節點獲取到半數以上的投票後就會進行故障恢復階段。

故障轉移:選舉成功的從節點取消複製變爲主節點,刪除故障節點的槽,並且將故障節點的槽委託到自己身上,向集羣廣播自己的 pong 消息,通知主機點的改變和接管了故障節點的槽信息。

作者:咔咔

簡介:從業三年,從搬磚一樣的生活方式換成了現在有“單”而居的日子。當然這個單不是單身的單!雖然極盡苛刻的技術學習但也遠不及客戶千奇百怪的要求。進入了朝九晚六,雖然躲過了風吹日曬,但是仍然很享受那些熬得只剩下黑眼圈的日子。堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。希望在諾大互聯網中咔咔的文章能帶給你一絲絲幫助。

編輯:陶家龍

猜你喜歡

1、Spark 背後的商業公司收購的 Redash 是個啥?

2、馬鐵大神的 Apache Spark 十年回顧

3、YARN 在字節跳動的優化與實踐

4、Apache Spark 3.0.0 正式版終於發佈了,重要特性全面解析

過往記憶大數據微信羣,請添加微信:fangzhen0219,備註【進羣】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章