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