App下載

與親生的Redis Cluster,來一次靈魂交流

猿友 2020-08-31 15:20:00 瀏覽數(shù) (3997)
反饋

文章轉(zhuǎn)載自公眾號:小姐姐的味道

筆者曾經(jīng)維護過千個 redis 實例,這些實例采用的簡單主從結(jié)構(gòu),集群方案主要是客戶端jar包。剛開始,個人并不是太喜歡redis cluster,因為它的路由實在是太死板,運維復雜。

但官方在推這個東西,注定了它的應用越來越廣泛,這在平常的交流中就能夠發(fā)現(xiàn)。雖然有這樣那樣的缺點,但總抵擋不了權(quán)威推動的浪潮。隨著redis cluster越來越穩(wěn)定,是時候和redis cluster來一次靈魂交流了。

簡介

redis cluster是親生的集群方案,目前,在高可用和穩(wěn)定性方面,都有了很大的進步。據(jù)統(tǒng)計和觀察,采用redis cluster架構(gòu)的公司和社區(qū)越來越多,已經(jīng)成為事實的標準。它的主要特點就是去中心化,無需proxy代理。其中一個主要設計目標就是達到線性可擴展性(linear scalability)。

僅僅靠redis cluster服務器本身,并不能完成官方承諾的功能。廣義上的redis cluster應該既包含redis服務器,又包含客戶端實現(xiàn)比如jedis等。它們是一個整體。

分布式存儲無非就是處理分片和副本。redis cluster來說,核心概念就是槽(slot),了解了它,基本就了解了集群的管理方式。

優(yōu)缺點

當了解這些特性以后,運維上其實是更簡單了。我們先看下比較明顯的優(yōu)缺點。

優(yōu)點

1、不再需要額外的Sentinel集群,為使用者提供了一致的方案,減少了學習成本。 2、去中心架構(gòu),節(jié)點對等,集群可支持上千個節(jié)點。 3、抽象出了slot概念,針對slot進行運維操作。 4、副本功能能夠?qū)崿F(xiàn)自動故障轉(zhuǎn)移,大部分情況下無需人工介入。

缺點

1、客戶端要緩存部分數(shù)據(jù),實現(xiàn)Cluster協(xié)議,相對復雜。 2、數(shù)據(jù)是通過異步復制的,不能保證數(shù)據(jù)的強一致性。 3、資源隔離困難,經(jīng)常流量不均衡,尤其是多個業(yè)務共用集群的時候。數(shù)據(jù)不知道在哪里,針對熱點數(shù)據(jù),也無法通過專項優(yōu)化完成。 4、從庫是完全的冷備,無法分擔讀操作,真是太太浪費了。需要做額外工作。 5、MultiOpPipeline支持有限,老代碼要是進行架構(gòu)升級,要小心了。 6、數(shù)據(jù)遷移是基于key而不是基于slot的,過程較慢。

基本原理

從槽到key,定位過程明顯就是一個雙層的路由。

key的路由

redis cluster和常用的一致性hash沒什么關系,它主要采用了哈希槽的概念。當需要在其中存取一個key時,redis客戶端會首先對這個key采用crc16算法算出一個值,然后對這個值進行mod操作。

crc16(key)mod 16384

key的路由

所以,每個key都會落在其中的一個hash槽上。16384 等同于 2^14(16k),redis節(jié)點發(fā)送心跳包時,需要把所有的槽信息放在這個心跳包里,所以要竭盡全力的優(yōu)化,感興趣的可以看下為什么默認的槽數(shù)量是 16384 。

服務端簡單原理

上面談到,redis cluster共定義了 16384 個槽,所有的集群操作都是圍繞著這個槽數(shù)據(jù)進行編碼。服務端使用一個簡單的數(shù)組存儲這些信息。

對于判斷有無的操作,使用bitmap來存儲是最節(jié)省空間的。redis cluster就是使用一個叫做slot的數(shù)組來保存當前節(jié)點是否持有了這個槽。

如圖,這個數(shù)組的長度為 16384/8=2048 Byte,那么就可以使用 0 或者 1 來標識本節(jié)點對某個槽是否擁有。

服務端簡單原理

其實,只需要第一份數(shù)據(jù)ClusterState也能完成操作,保存另外一個維度的Slot數(shù)組,能夠方便編碼和存儲。一個節(jié)點除了會將自己負責處理的槽記錄在兩個地方(clusterNode結(jié)構(gòu)的slots和numslots),它還會將自己的slots數(shù)組通過消息發(fā)送給集群中的其他節(jié)點,以此來告訴其他節(jié)點自己目前擁有的槽。

當數(shù)據(jù)庫中的 16384 個槽都有節(jié)點在處理時,集群處于上線狀態(tài)(ok);相反地,如果數(shù)據(jù)庫中有任何一個槽沒有得到處理,那么集群處于下線狀態(tài)(fail)。

當客戶端向節(jié)點發(fā)送相關命令時,接收命令的節(jié)點會計算出命令要處理的key屬于哪個槽,并檢查這個槽是否指派給了自己。如果不是自己的,會指引客戶端到正確的節(jié)點。

所以,客戶端連接集群中的任意一臺機器,都能夠完成操作。

安裝一個6節(jié)點集群

準備工作

假如我們要組裝一個3分片的集群,每個分片有一個副本。那么總共需要的node實例就有 3*2=6 個。redis可以通過指定配置文件的方式啟動,我們所做的工作就是修改配置文件。

復制6份默認的配置文件。

for i in {0..5}
do
cp redis.conf  redis-700$i.conf
done

修改其中的配置文件內(nèi)容,拿redis-7000.conf來說,我們要啟用它的cluster模式。

cluster-enabled yes
port 7000
cluster-config-file nodes-7000.conf

nodes-7000.conf會保存一些集群信息到當前節(jié)點,所以需要獨立。

啟動&關閉

同樣的,我們使用腳本來啟動它。

for i in {0..5}
do
nohup ./redis-server redis-700$i.conf &
done

為了演示,我們暴力把它關閉。

ps -ef| grep redis | awk '{print $2}' | xargs kill -9

組合集群

我們使用redis-cli進行集群的組合。redis將自動完成這個過程。這一系列的過程,是通過發(fā)送指令給每個節(jié)點進行組合的。

./redis-cli --cluster create 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 --cluster-replicas 1

與親生的Redis Cluster,來一次靈魂交流

幾個高級原理

節(jié)點故障

集群中的每個節(jié)點都會定期地向集群中的其他節(jié)點發(fā)送ping消息,以此來檢測對方是否在線,如果接收ping消息的節(jié)點沒有在規(guī)定的時間內(nèi)返回pong消息,那么發(fā)送ping消息的節(jié)點就會將接收ping消息的節(jié)點標記為疑似下線(PFAIL)。

如果在一個集群里面,半數(shù)以上節(jié)點都將某個主節(jié)點 x 報告為疑似下線,那么這個主節(jié)點x將被標記為已下線(FAIL),將x標記為FAIL的節(jié)點會向集群廣播一條關于 x 的FAIL消息,所有收到這條FAIL消息的節(jié)點都會立即將 x 標記為FAIL。

大家可以注意到這個過程,與 es 和 zk 的節(jié)點判斷類似,都是半數(shù)以上才進行判斷,所以主節(jié)點的數(shù)量一般都是奇數(shù)。由于沒有最小組群配置,理論上會有腦裂(暫時并未遇到過)。

主從切換

當一個節(jié)點發(fā)現(xiàn)自己的主節(jié)點進入fail狀態(tài),將會從這個節(jié)點的從節(jié)點當中,選出一臺,執(zhí)行slaveof no one命令,變身為主節(jié)點。

新的節(jié)點完成自己的槽指派以后,會向集群廣播一條pong消息,以便讓其他節(jié)點立即知道自己的這些變化。它告訴別人:我已經(jīng)是主節(jié)點了,我已經(jīng)接管了有問題的節(jié)點,成為了它的替身。

redis內(nèi)部對集群的這些管理,大量使用了已經(jīng)定義好的這些指令。所以這些指令不僅僅供我們從命令行使用,redis自己內(nèi)部也用。

數(shù)據(jù)同步

當一臺從機連接到master之后,會發(fā)送一個sync指令。master在收到這個指令后,會在后臺啟動存盤進程。執(zhí)行完畢后,master將整個數(shù)據(jù)庫文件傳輸?shù)?code>slave,這樣就完成了第一次全量同步。

接下來,master會把自己收到的變更指令,依次傳送給slave,從而達到數(shù)據(jù)的最終同步。從redis 2.8開始,就支持主從復制的斷點續(xù)傳,如果主從復制過程中,網(wǎng)絡連接斷掉了,那么可以接著上次復制的地方,繼續(xù)復制下去,而不是從頭開始復制一份。

數(shù)據(jù)丟失

redis cluster中節(jié)點之間使用異步復制,并沒有類似kafka這種ack的概念。節(jié)點之間通過gossip協(xié)議交換狀態(tài)信息,用投票機制完成SlaveMaster的角色提升,完成這個過程注定了需要時間。在發(fā)生故障的過程中就容易存在窗口,導致丟失寫入的數(shù)據(jù)。比如以下兩種情況。

一、命令已經(jīng)到到master,此時數(shù)據(jù)并沒有同步到slave,master會對客戶端回復ok。如果這個時候主節(jié)點宕機,那么這條數(shù)據(jù)將會丟失。redis這樣做會避免很多問題,但對一個對數(shù)據(jù)可靠性要求較高的系統(tǒng),是不可忍受的。

二、由于路由表是在客戶端存放的,存在一個時效問題。如果分區(qū)導致一個節(jié)點不可達,提升了某個從節(jié)點,但原來的主節(jié)點在這個時候又可以用了(并未完成failover)。這個時候一旦客戶端的路由表并沒有更新,那么它將會把數(shù)據(jù)寫到錯誤的節(jié)點,造成數(shù)據(jù)丟失。

所以redis cluster在通常情況下運行的很好,在極端情況下某些值丟失問題,目前無解。

復雜的運維

redis cluster的運維非常的繁雜,雖然已經(jīng)進行了抽象,但這個過程依然不簡單。有些指令,必須在詳細了解它的實現(xiàn)原理之后,才能真正放心的去用。

復雜的運維

上圖就是擴容會用到的一些命令。在實際使用的過程中,可能需要多次頻繁地輸入這些命令,且輸入的過程中還要監(jiān)視它的狀態(tài),所以基本上是不可能人工跑這些命令的。

運維的入口有兩個。一個是使用redis-cli連接任意一臺,然后發(fā)送cluster打頭的命令,這部分命令大多數(shù)是對槽進行操作。 在開始組合集群時,就是反復調(diào)用這些命令進行的具體邏輯執(zhí)行。

另外一個入口是使用redis-cli命令,加上--cluster參數(shù)和指令。這種形式主要是用來管控集群節(jié)點信息 ,比如增刪節(jié)點等。所以推薦使用這種方式。

redis cluster提供了非常復雜的命令,難于操作和記憶。推薦使用類似CacheCloud的工具進行管理。

下面是幾個例子。

通過向節(jié)點 A 發(fā)送 CLUSTER MEET 命令,客戶端可以讓接收命令的節(jié)點 A 將另一個節(jié)點 B 添加到節(jié)點 A 當前所在的集群里面:

CLUSTER MEET  127.0.0.1 7006

通過cluster addslots命令,可以將一個或者多個槽指派給某個節(jié)點負責。

127.0.0.1:7000> CLUSTER ADDSLOTS 0 1 2 3 4 . . . 5000

設置從節(jié)點。

CLUSTER REPLICATE <node_id>

redis-cli —cluster

redis-trib.rb是官方提供的Redis Cluster的管理工具,但最新版本已經(jīng)推薦使用redis-cli進行操作。

向集群中添加新節(jié)點

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7007 --cluster-replicas 1

從集群中刪除節(jié)點

redis-cli --cluster del-node 127.0.0.1:7006 54abb85ea9874af495057b6f95e0af5776b35a52

遷移槽到新的節(jié)點

redis-cli --cluster reshard 127.0.0.1:7006 --cluster-from 54abb85ea9874af495057b6f95e0af5776b35a52 --cluster-to 895e1d1f589dfdac34f8bdf149360fe9ca8a24eb  --cluster-slots 108

類似的命令還有很多。

create:創(chuàng)建集群 check:檢查集群 info:查看集群信息 fix:修復集群 reshard:在線遷移slot rebalance:平衡集群節(jié)點slot數(shù)量 add-node:添加新節(jié)點 del-node:刪除節(jié)點 set-timeout:設置節(jié)點的超時時間 call:在集群所有節(jié)點上執(zhí)行命令 import:將外部redis數(shù)據(jù)導入集群

其他方案概覽

主從模式

redis最早支持的,就是M-S模式,也就是一主多從。redis單機qps可達到 10w+,但是在某些高訪問量場景下,依然不太夠用。一般通過讀寫分離來增加slave,減少主機的壓力。

既然是主從架構(gòu),就面臨著同步問題,redis主從模式的同步分為全同步和部分同步。當剛創(chuàng)建一個從機的時候,不可避免的要進行一次全量同步。等全量同步結(jié)束之后,進入增量同步階段。這個和redis cluster是沒什么區(qū)別的。

這種模式還是比較穩(wěn)定的,但要額外做一些工作。用戶需要自行開發(fā)主從切換的功能,也就是使用哨兵去探測每個實例的健康狀況,然后通過指令進行集群狀態(tài)的改變。

當集群規(guī)模增大,主從模式會很快遇到瓶頸。所以一般會采用客戶端hash的方法進行擴展,包括類似于memcached的一致性哈希。

客戶端hash的路由

客戶端hash的路由可能會很復雜,通常會通過發(fā)布jar包或者配置的方式維護這些meta信息,這也給線上環(huán)境增加了很多不確定性。

不過,通過加入類似ZK主動通知的功能,將配置維護在云端,可以顯著降低風險。筆者曾經(jīng)維護過的幾千個redis節(jié)點,就是用這種方式進行管理的。

代理模式

代碼模式在redis cluster出現(xiàn)之前,非常流行,比如codis。代理層通過把自己模擬成一個redis,接收來自客戶端的請求,然后按照自定義的路由邏輯進行數(shù)據(jù)分片以及遷移,而業(yè)務方不需要改動任何代碼。除了能夠平滑的進行擴容,一些主從切換、FailOver的功能也在代理層完成,客戶端甚至可以沒有任何感知。這類程序又稱為分布式中間件。

一個典型的實現(xiàn)如下圖,背后的redis集群甚至可以是混合的。

與親生的Redis Cluster,來一次靈魂交流

但這種方式的缺點也是顯而易見的。首先,它引入了一個新的代理層,在結(jié)構(gòu)上、運維上都顯復雜。需要進行大量的編碼,比如failover、讀寫分離、數(shù)據(jù)遷移等。另外,proxy層的加入,對性能也有相應的損耗。

多個proxy一般使用lvs等前置進行負載均衡的設計,如果proxy層的機器很少而后端redis的流量很高,那么網(wǎng)卡會成為主要的瓶頸。

Nginx也可以作為redis的代理層,比較專業(yè)的說法叫做Smart Proxy。這種方式較為偏門,如果你對nginx比較熟悉,不失為一種優(yōu)雅的做法。

使用限制和坑

redis的速度特別的快。一般越快的東西,出問題的時候造成的后果越大。

不久之前,寫過一篇針對于redis的規(guī)范:《Redis規(guī)范,這可能是最中肯的了》。規(guī)范和架構(gòu)一樣,適合自己公司環(huán)境的,才是最好的,但會提供一些起碼的思路。

嚴格禁止的東西,一般都是前人踩坑的地方。除了這篇規(guī)范的內(nèi)容,對于redis-cluster,補充以下幾點。

1、redis cluster號稱能夠支持1k個節(jié)點,但你最好不要這么做。當節(jié)點數(shù)量增加到10,就能夠感受到集群的一些抖動。這么大的集群證明你的業(yè)務已經(jīng)很牛x了,考慮一下客戶端分片吧。

2、一定要避免產(chǎn)生熱點,如果流量全部打到了某個節(jié)點,后果一般很嚴重。

3、大key不要放redis,它會產(chǎn)生大量的慢查詢,影響正常的查詢。

4、如果你不是作為存儲,緩存一定要設置過期時間。占著茅坑不拉屎的感覺是非常討厭的。

5、大流量,不要開aof,開rdb即可。

6、redis cluster的操作,少用pipeline,少用multi-key,它們會產(chǎn)生大量不可預料的結(jié)果。

以上是一些補充,更全還是參考規(guī)范吧 《Redis規(guī)范,這可能是最中肯的了》。。。

End

redis 的代碼才那么一丁點,肯定不會實現(xiàn)非常復雜的分布式供能。 redis 的定位就是性能、水平伸縮和可用性,對于簡單的、一般流量的應用,已經(jīng)足夠了。生產(chǎn)環(huán)境無小事,對于復雜的高并發(fā)應用,注定了是一個組合的優(yōu)化方案。

以上就是W3Cschool編程獅關于與親生的Redis Cluster,來一次靈魂交流的相關介紹了,希望對大家有所幫助。

0 人點贊