文章轉(zhuǎn)載自公眾號(hào):小姐姐的味道
筆者曾經(jīng)維護(hù)過(guò)千個(gè) redis 實(shí)例,這些實(shí)例采用的簡(jiǎn)單主從結(jié)構(gòu),集群方案主要是客戶端jar包。剛開(kāi)始,個(gè)人并不是太喜歡redis cluster
,因?yàn)樗穆酚蓪?shí)在是太死板,運(yùn)維復(fù)雜。
但官方在推這個(gè)東西,注定了它的應(yīng)用越來(lái)越廣泛,這在平常的交流中就能夠發(fā)現(xiàn)。雖然有這樣那樣的缺點(diǎn),但總抵擋不了權(quán)威推動(dòng)的浪潮。隨著redis cluster
越來(lái)越穩(wěn)定,是時(shí)候和redis cluster
來(lái)一次靈魂交流了。
簡(jiǎn)介
redis cluster
是親生的集群方案,目前,在高可用和穩(wěn)定性方面,都有了很大的進(jìn)步。據(jù)統(tǒng)計(jì)和觀察,采用redis cluster
架構(gòu)的公司和社區(qū)越來(lái)越多,已經(jīng)成為事實(shí)的標(biāo)準(zhǔn)。它的主要特點(diǎn)就是去中心化,無(wú)需proxy
代理。其中一個(gè)主要設(shè)計(jì)目標(biāo)就是達(dá)到線性可擴(kuò)展性(linear scalability)。
僅僅靠redis cluster
服務(wù)器本身,并不能完成官方承諾的功能。廣義上的redis cluster
應(yīng)該既包含redis
服務(wù)器,又包含客戶端實(shí)現(xiàn)比如jedis
等。它們是一個(gè)整體。
分布式存儲(chǔ)無(wú)非就是處理分片和副本。 對(duì)redis cluster
來(lái)說(shuō),核心概念就是槽(slot),了解了它,基本就了解了集群的管理方式。
優(yōu)缺點(diǎn)
當(dāng)了解這些特性以后,運(yùn)維上其實(shí)是更簡(jiǎn)單了。我們先看下比較明顯的優(yōu)缺點(diǎn)。
優(yōu)點(diǎn)
1、不再需要額外的Sentinel
集群,為使用者提供了一致的方案,減少了學(xué)習(xí)成本。
2、去中心架構(gòu),節(jié)點(diǎn)對(duì)等,集群可支持上千個(gè)節(jié)點(diǎn)。
3、抽象出了slot
概念,針對(duì)slot
進(jìn)行運(yùn)維操作。
4、副本功能能夠?qū)崿F(xiàn)自動(dòng)故障轉(zhuǎn)移,大部分情況下無(wú)需人工介入。
缺點(diǎn)
1、客戶端要緩存部分?jǐn)?shù)據(jù),實(shí)現(xiàn)Cluster
協(xié)議,相對(duì)復(fù)雜。
2、數(shù)據(jù)是通過(guò)異步復(fù)制的,不能保證數(shù)據(jù)的強(qiáng)一致性。
3、資源隔離困難,經(jīng)常流量不均衡,尤其是多個(gè)業(yè)務(wù)共用集群的時(shí)候。數(shù)據(jù)不知道在哪里,針對(duì)熱點(diǎn)數(shù)據(jù),也無(wú)法通過(guò)專項(xiàng)優(yōu)化
完成。
4、從庫(kù)是完全的冷備,無(wú)法分擔(dān)讀操作,真是太太浪費(fèi)了。需要做額外工作。
5、MultiOp
和Pipeline
支持有限,老代碼要是進(jìn)行架構(gòu)升級(jí),要小心了。
6、數(shù)據(jù)遷移是基于key
而不是基于slot
的,過(guò)程較慢。
基本原理
從槽到key
,定位過(guò)程明顯就是一個(gè)雙層的路由。
key的路由
redis cluster
和常用的一致性hash
沒(méi)什么關(guān)系,它主要采用了哈希槽的概念。當(dāng)需要在其中存取一個(gè)key
時(shí),redis
客戶端會(huì)首先對(duì)這個(gè)key
采用crc16
算法算出一個(gè)值,然后對(duì)這個(gè)值進(jìn)行mod
操作。
crc16(key)mod 16384
所以,每個(gè)key
都會(huì)落在其中的一個(gè)hash
槽上。16384 等同于 2^14(16k),redis
節(jié)點(diǎn)發(fā)送心跳包時(shí),需要把所有的槽信息放在這個(gè)心跳包里,所以要竭盡全力的優(yōu)化,感興趣的可以看下為什么默認(rèn)的槽數(shù)量是 16384 。
服務(wù)端簡(jiǎn)單原理
上面談到,redis cluster
共定義了 16384 個(gè)槽,所有的集群操作都是圍繞著這個(gè)槽數(shù)據(jù)進(jìn)行編碼。服務(wù)端使用一個(gè)簡(jiǎn)單的數(shù)組存儲(chǔ)這些信息。
對(duì)于判斷有無(wú)的操作,使用bitmap
來(lái)存儲(chǔ)是最節(jié)省空間的。redis cluster
就是使用一個(gè)叫做slot
的數(shù)組來(lái)保存當(dāng)前節(jié)點(diǎn)是否持有了這個(gè)槽。
如圖,這個(gè)數(shù)組的長(zhǎng)度為 16384/8=2048 Byte
,那么就可以使用 0 或者 1 來(lái)標(biāo)識(shí)本節(jié)點(diǎn)對(duì)某個(gè)槽是否擁有。
其實(shí),只需要第一份數(shù)據(jù)ClusterState
也能完成操作,保存另外一個(gè)維度的Slot
數(shù)組,能夠方便編碼和存儲(chǔ)。一個(gè)節(jié)點(diǎn)除了會(huì)將自己負(fù)責(zé)處理的槽記錄在兩個(gè)地方(clusterNode結(jié)構(gòu)的slots和numslots),它還會(huì)將自己的slots
數(shù)組通過(guò)消息發(fā)送給集群中的其他節(jié)點(diǎn),以此來(lái)告訴其他節(jié)點(diǎn)自己目前擁有的槽。
當(dāng)數(shù)據(jù)庫(kù)中的 16384 個(gè)槽都有節(jié)點(diǎn)在處理時(shí),集群處于上線狀態(tài)(ok);相反地,如果數(shù)據(jù)庫(kù)中有任何一個(gè)槽沒(méi)有得到處理,那么集群處于下線狀態(tài)(fail)。
當(dāng)客戶端向節(jié)點(diǎn)發(fā)送相關(guān)命令時(shí),接收命令的節(jié)點(diǎn)會(huì)計(jì)算出命令要處理的key
屬于哪個(gè)槽,并檢查這個(gè)槽是否指派給了自己。如果不是自己的,會(huì)指引客戶端到正確的節(jié)點(diǎn)。
所以,客戶端連接集群中的任意一臺(tái)機(jī)器,都能夠完成操作。
安裝一個(gè)6節(jié)點(diǎn)集群
準(zhǔn)備工作
假如我們要組裝一個(gè)3分片的集群,每個(gè)分片有一個(gè)副本。那么總共需要的node
實(shí)例就有 3*2=6 個(gè)。redis
可以通過(guò)指定配置文件的方式啟動(dòng),我們所做的工作就是修改配置文件。
復(fù)制6份默認(rèn)的配置文件。
for i in {0..5}
do
cp redis.conf redis-700$i.conf
done
修改其中的配置文件內(nèi)容,拿redis-7000.conf
來(lái)說(shuō),我們要啟用它的cluster
模式。
cluster-enabled yes
port 7000
cluster-config-file nodes-7000.conf
nodes-7000.conf
會(huì)保存一些集群信息到當(dāng)前節(jié)點(diǎn),所以需要獨(dú)立。
啟動(dòng)&關(guān)閉
同樣的,我們使用腳本來(lái)啟動(dòng)它。
for i in {0..5}
do
nohup ./redis-server redis-700$i.conf &
done
為了演示,我們暴力把它關(guān)閉。
ps -ef| grep redis | awk '{print $2}' | xargs kill -9
組合集群
我們使用redis-cli
進(jìn)行集群的組合。redis
將自動(dòng)完成這個(gè)過(guò)程。這一系列的過(guò)程,是通過(guò)發(fā)送指令給每個(gè)節(jié)點(diǎn)進(jìn)行組合的。
./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
幾個(gè)高級(jí)原理
節(jié)點(diǎn)故障
集群中的每個(gè)節(jié)點(diǎn)都會(huì)定期地向集群中的其他節(jié)點(diǎn)發(fā)送ping消息,以此來(lái)檢測(cè)對(duì)方是否在線,如果接收ping
消息的節(jié)點(diǎn)沒(méi)有在規(guī)定的時(shí)間內(nèi)返回pong
消息,那么發(fā)送ping
消息的節(jié)點(diǎn)就會(huì)將接收ping
消息的節(jié)點(diǎn)標(biāo)記為疑似下線(PFAIL)。
如果在一個(gè)集群里面,半數(shù)以上節(jié)點(diǎn)都將某個(gè)主節(jié)點(diǎn) x 報(bào)告為疑似下線,那么這個(gè)主節(jié)點(diǎn)x將被標(biāo)記為已下線(FAIL),將x標(biāo)記為FAIL
的節(jié)點(diǎn)會(huì)向集群廣播一條關(guān)于 x 的FAIL
消息,所有收到這條FAIL
消息的節(jié)點(diǎn)都會(huì)立即將 x 標(biāo)記為FAIL
。
大家可以注意到這個(gè)過(guò)程,與 es 和 zk 的節(jié)點(diǎn)判斷類似,都是半數(shù)以上才進(jìn)行判斷,所以主節(jié)點(diǎn)的數(shù)量一般都是奇數(shù)。由于沒(méi)有最小組群配置,理論上會(huì)有腦裂(暫時(shí)并未遇到過(guò))。
主從切換
當(dāng)一個(gè)節(jié)點(diǎn)發(fā)現(xiàn)自己的主節(jié)點(diǎn)進(jìn)入fail
狀態(tài),將會(huì)從這個(gè)節(jié)點(diǎn)的從節(jié)點(diǎn)當(dāng)中,選出一臺(tái),執(zhí)行slaveof no one
命令,變身為主節(jié)點(diǎn)。
新的節(jié)點(diǎn)完成自己的槽指派以后,會(huì)向集群廣播一條pong
消息,以便讓其他節(jié)點(diǎn)立即知道自己的這些變化。它告訴別人:我已經(jīng)是主節(jié)點(diǎn)了,我已經(jīng)接管了有問(wèn)題的節(jié)點(diǎn),成為了它的替身。
redis
內(nèi)部對(duì)集群的這些管理,大量使用了已經(jīng)定義好的這些指令。所以這些指令不僅僅供我們從命令行使用,redis
自己內(nèi)部也用。
數(shù)據(jù)同步
當(dāng)一臺(tái)從機(jī)連接到master
之后,會(huì)發(fā)送一個(gè)sync
指令。master
在收到這個(gè)指令后,會(huì)在后臺(tái)啟動(dòng)存盤進(jìn)程。執(zhí)行完畢后,master
將整個(gè)數(shù)據(jù)庫(kù)文件傳輸?shù)?code>slave,這樣就完成了第一次全量同步。
接下來(lái),master
會(huì)把自己收到的變更指令,依次傳送給slave
,從而達(dá)到數(shù)據(jù)的最終同步。從redis 2.8
開(kāi)始,就支持主從復(fù)制的斷點(diǎn)續(xù)傳,如果主從復(fù)制過(guò)程中,網(wǎng)絡(luò)連接斷掉了,那么可以接著上次復(fù)制的地方,繼續(xù)復(fù)制下去,而不是從頭開(kāi)始復(fù)制一份。
數(shù)據(jù)丟失
redis cluster
中節(jié)點(diǎn)之間使用異步復(fù)制,并沒(méi)有類似kafka
這種ack
的概念。節(jié)點(diǎn)之間通過(guò)gossip
協(xié)議交換狀態(tài)信息,用投票機(jī)制完成Slave
到Master
的角色提升,完成這個(gè)過(guò)程注定了需要時(shí)間。在發(fā)生故障的過(guò)程中就容易存在窗口,導(dǎo)致丟失寫(xiě)入的數(shù)據(jù)。比如以下兩種情況。
一、命令已經(jīng)到到master
,此時(shí)數(shù)據(jù)并沒(méi)有同步到slave
,master
會(huì)對(duì)客戶端回復(fù)ok。如果這個(gè)時(shí)候主節(jié)點(diǎn)宕機(jī),那么這條數(shù)據(jù)將會(huì)丟失。redis
這樣做會(huì)避免很多問(wèn)題,但對(duì)一個(gè)對(duì)數(shù)據(jù)可靠性要求較高的系統(tǒng),是不可忍受的。
二、由于路由表是在客戶端存放的,存在一個(gè)時(shí)效問(wèn)題。如果分區(qū)導(dǎo)致一個(gè)節(jié)點(diǎn)不可達(dá),提升了某個(gè)從節(jié)點(diǎn),但原來(lái)的主節(jié)點(diǎn)在這個(gè)時(shí)候又可以用了(并未完成failover)。這個(gè)時(shí)候一旦客戶端的路由表并沒(méi)有更新,那么它將會(huì)把數(shù)據(jù)寫(xiě)到錯(cuò)誤的節(jié)點(diǎn),造成數(shù)據(jù)丟失。
所以redis cluster
在通常情況下運(yùn)行的很好,在極端情況下某些值丟失問(wèn)題,目前無(wú)解。
復(fù)雜的運(yùn)維
redis cluster
的運(yùn)維非常的繁雜,雖然已經(jīng)進(jìn)行了抽象,但這個(gè)過(guò)程依然不簡(jiǎn)單。有些指令,必須在詳細(xì)了解它的實(shí)現(xiàn)原理之后,才能真正放心的去用。
上圖就是擴(kuò)容會(huì)用到的一些命令。在實(shí)際使用的過(guò)程中,可能需要多次頻繁地輸入這些命令,且輸入的過(guò)程中還要監(jiān)視它的狀態(tài),所以基本上是不可能人工跑這些命令的。
運(yùn)維的入口有兩個(gè)。一個(gè)是使用redis-cli連接任意一臺(tái),然后發(fā)送cluster
打頭的命令,這部分命令大多數(shù)是對(duì)槽進(jìn)行操作。 在開(kāi)始組合集群時(shí),就是反復(fù)調(diào)用這些命令進(jìn)行的具體邏輯執(zhí)行。
另外一個(gè)入口是使用redis-cli命令,加上--cluster
參數(shù)和指令。這種形式主要是用來(lái)管控集群節(jié)點(diǎn)信息 ,比如增刪節(jié)點(diǎn)等。所以推薦使用這種方式。
redis cluster
提供了非常復(fù)雜的命令,難于操作和記憶。推薦使用類似CacheCloud
的工具進(jìn)行管理。
下面是幾個(gè)例子。
通過(guò)向節(jié)點(diǎn) A 發(fā)送 CLUSTER MEET
命令,客戶端可以讓接收命令的節(jié)點(diǎn) A 將另一個(gè)節(jié)點(diǎn) B 添加到節(jié)點(diǎn) A 當(dāng)前所在的集群里面:
CLUSTER MEET 127.0.0.1 7006
通過(guò)cluster addslots
命令,可以將一個(gè)或者多個(gè)槽指派給某個(gè)節(jié)點(diǎn)負(fù)責(zé)。
127.0.0.1:7000> CLUSTER ADDSLOTS 0 1 2 3 4 . . . 5000
設(shè)置從節(jié)點(diǎn)。
CLUSTER REPLICATE <node_id>
redis-cli —cluster
redis-trib.rb
是官方提供的Redis Cluster
的管理工具,但最新版本已經(jīng)推薦使用redis-cli
進(jìn)行操作。
向集群中添加新節(jié)點(diǎn)
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7007 --cluster-replicas 1
從集群中刪除節(jié)點(diǎn)
redis-cli --cluster del-node 127.0.0.1:7006 54abb85ea9874af495057b6f95e0af5776b35a52
遷移槽到新的節(jié)點(diǎn)
redis-cli --cluster reshard 127.0.0.1:7006 --cluster-from 54abb85ea9874af495057b6f95e0af5776b35a52 --cluster-to 895e1d1f589dfdac34f8bdf149360fe9ca8a24eb --cluster-slots 108
類似的命令還有很多。
create:創(chuàng)建集群 check:檢查集群 info:查看集群信息 fix:修復(fù)集群 reshard:在線遷移slot rebalance:平衡集群節(jié)點(diǎn)slot數(shù)量 add-node:添加新節(jié)點(diǎn) del-node:刪除節(jié)點(diǎn) set-timeout:設(shè)置節(jié)點(diǎn)的超時(shí)時(shí)間 call:在集群所有節(jié)點(diǎn)上執(zhí)行命令 import:將外部redis數(shù)據(jù)導(dǎo)入集群
其他方案概覽
主從模式
redis
最早支持的,就是M-S
模式,也就是一主多從。redis
單機(jī)qps
可達(dá)到 10w+,但是在某些高訪問(wèn)量場(chǎng)景下,依然不太夠用。一般通過(guò)讀寫(xiě)分離來(lái)增加slave
,減少主機(jī)的壓力。
既然是主從架構(gòu),就面臨著同步問(wèn)題,redis
主從模式的同步分為全同步和部分同步。當(dāng)剛創(chuàng)建一個(gè)從機(jī)的時(shí)候,不可避免的要進(jìn)行一次全量同步。等全量同步結(jié)束之后,進(jìn)入增量同步階段。這個(gè)和redis cluster
是沒(méi)什么區(qū)別的。
這種模式還是比較穩(wěn)定的,但要額外做一些工作。用戶需要自行開(kāi)發(fā)主從切換的功能,也就是使用哨兵去探測(cè)每個(gè)實(shí)例的健康狀況,然后通過(guò)指令進(jìn)行集群狀態(tài)的改變。
當(dāng)集群規(guī)模增大,主從模式會(huì)很快遇到瓶頸。所以一般會(huì)采用客戶端hash
的方法進(jìn)行擴(kuò)展,包括類似于memcached
的一致性哈希。
客戶端hash
的路由可能會(huì)很復(fù)雜,通常會(huì)通過(guò)發(fā)布jar
包或者配置的方式維護(hù)這些meta
信息,這也給線上環(huán)境增加了很多不確定性。
不過(guò),通過(guò)加入類似ZK
主動(dòng)通知的功能,將配置維護(hù)在云端,可以顯著降低風(fēng)險(xiǎn)。筆者曾經(jīng)維護(hù)過(guò)的幾千個(gè)redis
節(jié)點(diǎn),就是用這種方式進(jìn)行管理的。
代理模式
代碼模式在redis cluster
出現(xiàn)之前,非常流行,比如codis
。代理層通過(guò)把自己模擬成一個(gè)redis
,接收來(lái)自客戶端的請(qǐng)求,然后按照自定義的路由邏輯進(jìn)行數(shù)據(jù)分片以及遷移,而業(yè)務(wù)方不需要改動(dòng)任何代碼。除了能夠平滑的進(jìn)行擴(kuò)容,一些主從切換、FailOver
的功能也在代理層完成,客戶端甚至可以沒(méi)有任何感知。這類程序又稱為分布式中間件。
一個(gè)典型的實(shí)現(xiàn)如下圖,背后的redis
集群甚至可以是混合的。
但這種方式的缺點(diǎn)也是顯而易見(jiàn)的。首先,它引入了一個(gè)新的代理層,在結(jié)構(gòu)上、運(yùn)維上都顯復(fù)雜。需要進(jìn)行大量的編碼,比如failover
、讀寫(xiě)分離、數(shù)據(jù)遷移等。另外,proxy
層的加入,對(duì)性能也有相應(yīng)的損耗。
多個(gè)proxy
一般使用lvs
等前置進(jìn)行負(fù)載均衡的設(shè)計(jì),如果proxy
層的機(jī)器很少而后端redis
的流量很高,那么網(wǎng)卡會(huì)成為主要的瓶頸。
Nginx
也可以作為redis
的代理層,比較專業(yè)的說(shuō)法叫做Smart Proxy
。這種方式較為偏門,如果你對(duì)nginx
比較熟悉,不失為一種優(yōu)雅的做法。
使用限制和坑
redis
的速度特別的快。一般越快的東西,出問(wèn)題的時(shí)候造成的后果越大。
不久之前,寫(xiě)過(guò)一篇針對(duì)于redis
的規(guī)范:《Redis規(guī)范,這可能是最中肯的了》。規(guī)范和架構(gòu)一樣,適合自己公司環(huán)境的,才是最好的,但會(huì)提供一些起碼的思路。
嚴(yán)格禁止的東西,一般都是前人踩坑的地方。除了這篇規(guī)范的內(nèi)容,對(duì)于redis-cluster
,補(bǔ)充以下幾點(diǎn)。
1、redis cluster
號(hào)稱能夠支持1k個(gè)節(jié)點(diǎn),但你最好不要這么做。當(dāng)節(jié)點(diǎn)數(shù)量增加到10,就能夠感受到集群的一些抖動(dòng)。這么大的集群證明你的業(yè)務(wù)已經(jīng)很牛x了,考慮一下客戶端分片吧。
2、一定要避免產(chǎn)生熱點(diǎn),如果流量全部打到了某個(gè)節(jié)點(diǎn),后果一般很嚴(yán)重。
3、大key
不要放redis
,它會(huì)產(chǎn)生大量的慢查詢,影響正常的查詢。
4、如果你不是作為存儲(chǔ),緩存一定要設(shè)置過(guò)期時(shí)間。占著茅坑不拉屎的感覺(jué)是非常討厭的。
5、大流量,不要開(kāi)aof
,開(kāi)rdb
即可。
6、redis cluster
的操作,少用pipeline
,少用multi-key
,它們會(huì)產(chǎn)生大量不可預(yù)料的結(jié)果。
以上是一些補(bǔ)充,更全還是參考規(guī)范吧 《Redis規(guī)范,這可能是最中肯的了》。。。
End
redis 的代碼才那么一丁點(diǎn),肯定不會(huì)實(shí)現(xiàn)非常復(fù)雜的分布式供能。 redis 的定位就是性能、水平伸縮和可用性,對(duì)于簡(jiǎn)單的、一般流量的應(yīng)用,已經(jīng)足夠了。生產(chǎn)環(huán)境無(wú)小事,對(duì)于復(fù)雜的高并發(fā)應(yīng)用,注定了是一個(gè)組合的優(yōu)化方案。
以上就是W3Cschool編程獅
關(guān)于與親生的Redis Cluster,來(lái)一次靈魂交流的相關(guān)介紹了,希望對(duì)大家有所幫助。