面試問Redis集群,被虐的不行了......
原創(chuàng)【51CTO.com原創(chuàng)稿件】 上一篇我們講解了 Redis 哨兵的工作原理,哨兵主要針對單節(jié)點故障無法自動恢復(fù)的解決方案,集群主要針對單節(jié)點容量、并發(fā)問題、線性可擴展性的解決方案。
本篇我將講解 Redis 集群的工作原理,文末有你們想要的設(shè)置 SSH 背景哦!
本文主要圍繞如下幾個方面介紹集群:
- 集群簡介
- 集群作用
- 配置集群
- 手動、自動故障轉(zhuǎn)移
- 故障轉(zhuǎn)移原理
本文實現(xiàn)環(huán)境:
- CentOS 7.3
- Redis 4.0
- Redis 工作目錄 /usr/local/redis
- 所有操作均在虛擬機模擬進(jìn)行
集群簡介
集群是為了解決主從復(fù)制中單機內(nèi)存上限和并發(fā)問題,假如你現(xiàn)在的云服務(wù)內(nèi)存為 256GB,當(dāng)達(dá)到這個內(nèi)存時 Redis 就沒辦法再提供服務(wù)。
同時數(shù)據(jù)量能達(dá)到這個地步寫數(shù)據(jù)量也會很大,容易造成緩沖區(qū)溢出,造成從節(jié)點無限的進(jìn)行全量復(fù)制導(dǎo)致主從無法正常工作。
那么我們就需要把單機的主從改為多對多的方式,并且所有的主節(jié)點都會連接在一起互相通信。
這樣的方式既可以分擔(dān)單機內(nèi)存,也可以分發(fā)請求,提高系統(tǒng)的可用性。
如下圖:當(dāng)有大量請求寫入時,不再會單一的向一個主節(jié)點發(fā)送指令,而會把指令進(jìn)行分流到各個主節(jié)點,達(dá)到分擔(dān)內(nèi)存、避免大量請求的作用。
那么指令是如何進(jìn)行分流存儲的呢?我們就需要到集群存儲結(jié)構(gòu)中一探究竟。
集群作用
集群的作用有如下幾個:
- 分散單機的存儲能力,同時也可以很方便的實現(xiàn)擴展。
- 分流單機的訪問請求。
- 提高系統(tǒng)的可用性。
如何理解提高系統(tǒng)的可用性這句話,我們看下圖,當(dāng) master1 宕機后對系統(tǒng)的影響不會那么大,仍然可以提供正常的服務(wù)。
這個時候就會有人問了,當(dāng) master1 宕機后,集群這個時候怎么工作呀?這個問題會在下文的故障轉(zhuǎn)移來給你解答,并且在原理篇會對這個問題進(jìn)行詳解。
集群存儲結(jié)構(gòu)
存儲結(jié)構(gòu)
單機的存儲是當(dāng)用戶發(fā)起請求后直接把 key 存儲到自己的內(nèi)存即可。
集群的存儲結(jié)構(gòu)就沒有那么簡單了,首先當(dāng)用戶發(fā)起一個 key 指令后需要做的事情如下:
- 通過 CRC16(key) 會計算出來一個值。
- 用這個值取模 16384,會得到一個值,我們就先認(rèn)為是 28。
- 這個值 28 就是 key 保存的空間位置。
那么現(xiàn)在問題來了,這個 key 到底應(yīng)該存儲在哪個 Redis 存儲空間里邊呢?
其實 Redis 在集群啟動后就已經(jīng)把存儲空間劃分了 16384 份,每臺主機保存一部分。
這里需要注意的是我給每個 Redis 存儲空間里邊的編號就相當(dāng)于一個小的存儲空間(專業(yè)術(shù)語“哈希槽”)。
你可以理解為一棟樓里邊的編號,一棟樓就是 Redis 的整個存儲空間,每個房子的編號就相當(dāng)于一個存儲空間,這個存儲空間會有一定的區(qū)域來保存對應(yīng)的 key,并非上圖取模后的位置。
箭頭指向的 28 是指的 28 會存儲在這個區(qū)域里,這個房子有可能會存儲 29、30、31 等。
此時問題來了,如果新增、減少一臺機器后怎么辦呢?看圖說話,能用圖說明盡量不去用文字。
在新增一臺機器后,會從其他三個存儲空間中拿出一定的槽分配給新的機器。這里可以自己設(shè)置想給新的機器放多少個槽。
同樣減少一臺機器后會把去掉的槽在重新分配給其它現(xiàn)有的機器跟新增節(jié)點一樣,可以指定節(jié)點接收槽。
所謂的增節(jié)點或去節(jié)點就是改變槽所存儲的位置不同。
了解了集群的存儲結(jié)構(gòu)后,我們就需要在對另一個問題進(jìn)行說明了,集群是如何設(shè)計內(nèi)部通訊呢?
來了一個值,獲取一個 key,去哪拿數(shù)據(jù)?跟著這個問題我們看下文。
通訊設(shè)計
集群中的每個節(jié)點會在一定的時期給其它節(jié)點發(fā)送 ping 消息,其他節(jié)點返回 pong 作為響應(yīng)。
經(jīng)過一段時間后所有節(jié)點都會知道集群全部節(jié)點的槽信息。如下圖有三個節(jié)點,那么就會把 16384 個哈希槽分成三份。
分別為:
- 0-5500
- 5501-11000
- 11001-16384
當(dāng)用戶發(fā)起了一個 key 的請求,集群是如何處理請求的呢?上圖的黑框代表這集群所有節(jié)點的槽信息,里邊還有很多其它信息。
如圖所示,用戶發(fā)起請求 key,Redis 接收后計算 key 的槽位置,在根據(jù)槽位置找出對應(yīng)的節(jié)點。
如果訪問的槽就在節(jié)點本身,那么就會直接返回 key 對應(yīng)數(shù)據(jù)。否則會回復(fù) moved 重定向錯誤,并且給客戶端返回正確的節(jié)點。
然后重發(fā) key 指令,如下圖:
配置集群
①修改配置文件
如下圖:
只需要注意圈中的配置信息即可:
- cluster-enabled yes:開啟集群模式。
- cluster-config-file nodes-6379.conf:集群配置文件。
- clustre-node-timeout 10000:節(jié)點超時時間,這里為了方便測試設(shè)置為 10s。
②構(gòu)建 6 個節(jié)點的配置文件并全啟動
給大家提供一個命令可以很方便的替換文件:
- sed 's/6379/6380/g' 6379-redis.conf > 6380-redis.conf
按照這樣的方式創(chuàng)建出來 6 個不同端口的配置文件:
隨便打開一個配置文件查看,檢測是否替換成功:
為了查看日志信息方便,全部使用前臺啟動。并且查看服務(wù)是否都正常啟動,執(zhí)行命令:
- ps -ef | grep redis
可以看到啟動后多了個 cluster 標(biāo)識,代表著都是集群的一個節(jié)點。
所有節(jié)點啟動完成,集群啟動的指令需要基于 Ruby(本人使用 Redis 版本為 4.0),接下來一起安裝。
③安裝 Ruby
執(zhí)行命令:
- wget https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.gz
解壓(根據(jù)自己下載的版本來解壓):
- tar -xvzf ruby-2.7.1.tar.gz
安裝:
- ./configure | make | make install
這三個指令一氣呵成,查看 ruby 和 gem 版本:ruby -v。
④啟動集群
集群的執(zhí)行命令在 /usr/local/redis/src/redis-trib.rb,注意如果需要直接使用 redis-trib.rb 命令,需要 ln 到 bin 目錄下,否則就必須使用 ./redis-trib.rb 的方式。
如果按照步驟走,這里會出現(xiàn)一個錯誤,如下圖:
執(zhí)行 gem install redis,很不幸的是在這里也會出現(xiàn)錯誤:
隨后需要安裝 yum install zlib-devel 和 yum install openssl-devel。
安裝完成后,在 /ruby-2.7.1/ext/openssl 和 /ruby-2.7.1/ext/zlib 分別執(zhí)行 ruby extconf.rb,并且執(zhí)行 make | make install。
然后再執(zhí)行 gem install redis 就 OK:
這時再回頭來執(zhí)行:
- ./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
信息解讀:創(chuàng)建集群,并且給 6 個節(jié)點分配哈希槽,后三個節(jié)點配置為前三個節(jié)點的從節(jié)點。
顯示每個節(jié)點的哈希槽信息和節(jié)點 ID,最后一步需要輸入 yes:
來到 data 目錄下查看配置文件的變化。配置文件主要信息是每個主節(jié)點分的槽:
查看主機點的運行日志:這里給的主要信息 cluster status changed:ok 集群狀態(tài)正常。
⑤集群設(shè)置與獲取數(shù)據(jù)
當(dāng)直接設(shè)置數(shù)據(jù)會報錯,并且把 name 這個 key 進(jìn)行轉(zhuǎn)化后的槽位置為 5798 并且給出了 ip 地址和端口號。
需要使用命令 redis-cli -c,在進(jìn)行設(shè)置值的時候提示說重定向到 5798 的這個槽。
接下來進(jìn)行獲取數(shù)據(jù),會自動的切換節(jié)點:
故障轉(zhuǎn)移
①集群從節(jié)點下線
根據(jù)上文集群啟動信息知道端口 6383 是 6379 的從節(jié)點。接下來就是讓 6383 下線查看 6379 的日志信息。
6379 會報出連接 6383 丟失,并且給上標(biāo)記 fail,表示不可用。這個時候集群還是正常工作的。
總結(jié):從節(jié)點下線對集群沒有影響。
當(dāng)端口 6383 上線后,所有的節(jié)點會把 fail 的標(biāo)記清除,如下圖:
②集群主節(jié)點下線
手動下線主節(jié)點 6379,查看從節(jié)點 6383 日志信息,此時的 6383 節(jié)點會持續(xù)連接 6379 共計 10 次,那為什么是 10 次呢?
是根據(jù)我們配置的參數(shù) cluster-node-timeout 10 來決定的,這里給我們一個信息就是一秒連接一次。
直到時間到期后,開始故障轉(zhuǎn)移。這時 6383 在故障轉(zhuǎn)移選舉中勝任,翻身奴隸把歌唱,成為了主節(jié)點。
此時在查看一下集群的節(jié)點信息,命令 cluster nodes。會發(fā)現(xiàn)這里竟然存在四個主節(jié)點,但是其中一個主節(jié)點時下線狀態(tài):
6379 原主節(jié)點上線:6379 上線后,同樣所有的節(jié)點也會清除 fail 信息。并且節(jié)點信息也會改變,此時的 6379 改變?yōu)?6383 的從節(jié)點。
③新增主節(jié)點
再新增倆個端口 6385 和 6386:
執(zhí)行新增命令 ./redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379,這里發(fā)送的就是 meet 消息。
執(zhí)行 add-node 命令,第一個參數(shù)為新節(jié)點的 ip+端口,第二個參數(shù)為已存在集群中的節(jié)點。根據(jù)下圖我們就可以看到新增的節(jié)點已經(jīng)存在集群中了。
注意:雖說 6385 已經(jīng)成為集群中的節(jié)點了,但是跟其它節(jié)點有區(qū)別。它沒有數(shù)據(jù),也就是沒有哈希槽。
接下來我們就需要把集群中的某些哈希槽分配到這個新節(jié)點上,分配結(jié)束后這個節(jié)點才會成為真正意義上的主節(jié)點。
執(zhí)行命令 ./redis-trib.rb reshard 127.0.0.1:6385,會提示轉(zhuǎn)移多少個哈希槽并填寫接收節(jié)點的 id。
最后一步詢問是否從所有節(jié)點中轉(zhuǎn)移:我使用的是 all。使用指令:cluster nodes 查看,6385 的這個節(jié)點就已經(jīng)擁有三個范圍的哈希槽了。
主節(jié)點已經(jīng)新增好了,接下來就需要給 6385 這個主節(jié)點配置一個從節(jié)點 6386,命令如下:
./redis-trib.rb add-node --slave --master-id dcc0ec4d0c932ac5c35ae76af4f9c5d27a422d9f 127.0.0.1:6386 127.0.0.1:6385
master-id 是 6385 的 id,第一個參數(shù)為新節(jié)點的 ip+端口,第二個為指定的主節(jié)點 ip+端口。
④手動故障遷移
當(dāng)想對集群中的主節(jié)點進(jìn)行升級的話可以手動執(zhí)行故障轉(zhuǎn)移到從節(jié)點,避免集群可用性受影響。
在從節(jié)點執(zhí)行命令:cluster failover。
執(zhí)行過程:查看節(jié)點信息就可以看到 6386 這個節(jié)點已經(jīng)成為了主機點。
當(dāng)給從節(jié)點發(fā)送 cluster failover 指令后,從節(jié)點會給主節(jié)點發(fā)送 CLUSTERMSG_TYPE_MFSTART 包。從節(jié)點請求主節(jié)點停止訪問,從而對比兩者的數(shù)據(jù)偏移量達(dá)到一致。
這時客戶端不會連接我們淘汰的主節(jié)點,同時主節(jié)點向從節(jié)點發(fā)送復(fù)制偏移量,從節(jié)點得到復(fù)制偏移量后故障轉(zhuǎn)移開始,接著通知主節(jié)點進(jìn)行配置切換。
當(dāng)客戶端在舊的 master 上解鎖后,重新連接到新的主節(jié)點上。
故障轉(zhuǎn)移原理篇
上文中我們測試了故障轉(zhuǎn)移,主節(jié)點下線后從節(jié)點變?yōu)橹鞴?jié)點,接下來剖析這個過程。
①故障發(fā)現(xiàn)到確認(rèn)
集群中的每個節(jié)點會定期的給其它節(jié)點發(fā)送 ping 消息,接收方用 pong 作為回復(fù)。
如果在 cluster-node-timeout 的時間內(nèi) ping 消息一直失敗,則會把接收方的節(jié)點標(biāo)記為 pfail 狀態(tài)也就是主觀下線。
這個下線狀態(tài)是不是很熟悉?沒錯,這個跟哨兵判斷主節(jié)點是否異常有點相似。
當(dāng)一個哨兵發(fā)現(xiàn)主節(jié)點有問題時也會標(biāo)記主節(jié)點客觀下線(s_down)。突然發(fā)現(xiàn)跑題了,尷尬......
再提一下哨兵,當(dāng)一個哨兵認(rèn)為主節(jié)點異常后標(biāo)記主觀下線,但是其他哨兵怎么能會同意,不能你說什么就是什么。
都會去嘗試連接異常的主節(jié)點,當(dāng)半數(shù)以上的哨兵都認(rèn)為主節(jié)點異常后會直接讓其主節(jié)點客觀下線。
同樣集群也不會因為一個節(jié)點判斷其狀態(tài)為下線就行的,節(jié)點直接通過 Gossip 消息傳播,集群中節(jié)點會不斷收集故障節(jié)點的下線反饋并且存儲到本地的故障節(jié)點下線報告中。
當(dāng)有半數(shù)以上的集群主節(jié)點都標(biāo)記為主觀下線后改變狀態(tài)為客觀下線。最后向集群廣播一條 fail 消息,通知所有節(jié)點將故障節(jié)點標(biāo)記為客觀下線。
例如:節(jié)點 A 發(fā)送 ping 到節(jié)點 B 通信異常后標(biāo)記節(jié)點 B 為 pfail,之后節(jié)點 A 會繼續(xù)給節(jié)點 C 發(fā)送 ping,并且攜帶節(jié)點 B 的 pfail 信息,然后節(jié)點 C 將節(jié)點 B 的故障保存到下線報告中。
當(dāng)下線報告數(shù)量大于有哈希槽主節(jié)點的一半數(shù)量以上后就會嘗試客觀下線。
②故障恢復(fù)(從節(jié)點從此翻身奴隸把歌唱)
當(dāng)故障節(jié)點被定義為客觀下線后,故障節(jié)點的所有從節(jié)點承擔(dān)故障恢復(fù)的責(zé)任。
故障恢復(fù)是從節(jié)點通過定時任務(wù)發(fā)現(xiàn)自己的主機點客觀下線后就會執(zhí)行故障恢復(fù)流程。
資格檢查:所有的從節(jié)點都會進(jìn)行檢查與主節(jié)點最后的連接時間,斷線時間大于 cluster-node-time*cluster-slave-validity-factor 時不具備故障轉(zhuǎn)移的資格。
準(zhǔn)備選舉時間:先說說為什么這里會有一個準(zhǔn)備選舉時間。資格檢查過后存在多個從節(jié)點,那么就需要使用不同的延遲選舉時間來支持優(yōu)先級。
這里的優(yōu)先級就是以復(fù)制偏移量為基準(zhǔn),偏移量越大與故障主節(jié)點的延遲越小,那么就更有機會擁有替換主節(jié)點的機會。主要的作用就是確保數(shù)據(jù)一致性最好的節(jié)點優(yōu)先發(fā)起選舉。
選舉投票:Redis 集群的投票機制沒有采用從節(jié)點進(jìn)行領(lǐng)導(dǎo)選舉,這點切記不要跟哨兵搞混了。集群的投票機制都是持有槽的主機點進(jìn)行投票的。
故障節(jié)點的從節(jié)點會廣播一個 FAILOVER_AUTH_REQUEST 數(shù)據(jù)包給所有的持有槽的主節(jié)點請求投票。
當(dāng)主節(jié)點回復(fù) FAILOVER_AUTH_ACK 投票后在 NODE_TIMEOUT * 2 的這段時間不能給其他的從節(jié)點投票。從節(jié)點獲取到半數(shù)以上的投票后就會進(jìn)行故障恢復(fù)階段。
故障轉(zhuǎn)移:選舉成功的從節(jié)點取消復(fù)制變?yōu)橹鞴?jié)點,刪除故障節(jié)點的槽,并且將故障節(jié)點的槽委托到自己身上,向集群廣播自己的 pong 消息,通知主機點的改變和接管了故障節(jié)點的槽信息。
福利:你們想要的 SSH 的背景
上一篇利用兩個夜晚才弄完的 Redis 哨兵文章,結(jié)果你們的關(guān)注點卻不在文章本身,啊!小編心很痛......
為了滿足大家的要求,我忍痛說一下如何設(shè)置亮瞎的背景,我使用的工具是xsheel。
打開工具選擇選項 :
接著到查看有個窗口透明就可以設(shè)置 xsheel 透明了。
對嘍!你想的沒錯,這就是桌面背景,是不是準(zhǔn)備開始設(shè)置去了?最后,歡迎各路大神給予技術(shù)點補充和辨錯。
作者:咔咔
簡介:從業(yè)三年,從搬磚一樣的生活方式換成了現(xiàn)在有“單”而居的日子。當(dāng)然這個單不是單身的單!雖然極盡苛刻的技術(shù)學(xué)習(xí)但也遠(yuǎn)不及客戶千奇百怪的要求。進(jìn)入了朝九晚六,雖然躲過了風(fēng)吹日曬,但是仍然很享受那些熬得只剩下黑眼圈的日子。堅持學(xué)習(xí)、堅持寫博、堅持分享是咔咔從業(yè)以來一直所秉持的信念。希望在諾大互聯(lián)網(wǎng)中咔咔的文章能帶給你一絲絲幫助。
編輯:陶家龍
征稿:有投稿、尋求報道意向技術(shù)人請聯(lián)絡(luò) editor@51cto.com
【51CTO原創(chuàng)稿件,合作站點轉(zhuǎn)載請注明原文作者和出處為51CTO.com】