全面剖析Redis Cluster原理和應用 (good)

redishtml

redis cluster注意的問題 :node

一、‘cluster-require-full-coverage’參數的設置。該參數是redis配置文件中cluster模式的一個參數,從字面上基本就能看出它的做用:須要所有覆蓋!
具體點是redis cluster須要16384個slot都正常的時候才能對外提供服務,換句話說,只要任何一個slot異常那麼整個cluster不對外提供服務。
redis默認是‘yes’,即須要全覆蓋!建議設置成‘no’。git

二、阻塞命令產生failover。因爲一些阻塞命令(flushall, del key1 key2 …)會形成redis在‘cluster-node-timeout’時間內沒法響應其餘節點的ping請求,
從而致使其餘節點都把該redis標記出了pfail狀態,進而產生failover。redis做者計劃使用lazy redis解決。github

三、鏈接創建。當redis cluster的節點數多了之後,client對每一個節點創建一個tcp鏈接須要花比較多的時間。若是是長鏈接,用戶只需忍受一次鏈接創建的過程,
若是是短鏈接,那麼頻繁創建鏈接將會極大的下降效率。但即使是短鏈接,只要每次請求只涉及到一個key,有些客戶端可能只須要與一個節點創建鏈接。redis

四、Jedis。Jedis是redis最流行的Java客戶端,支持redis cluster。
‘MaxRedirectionsException’異常,出現該異常說明剛剛執行的那條命令通過屢次重試,沒有執行成功,須要用戶再次執行。
在cluster擴容或者slot遷移的時候比較容易出現。建議捕獲該異常並採起相應重試工做。
Pipeline,Jedis目前不支持cluster模式的pipeline,建議採用多併發代替pipeline。算法

五、Multi-key。Redis cluster對多key操做有限,要求命令中全部的key都屬於一個slot,才能夠被執行。客戶端能夠對multi-key命令進行拆分,再發給redis。
另一個侷限是,在slot遷移過程當中,multi-key命令特別容易報錯(CROSSSLOT Keys in request don’t hash to the same slot)。建議不用multi-key命令。sql

六、擴容速度慢。redis官方提供了redis cluster管理腳本redis-trib.rb。使用該腳本進行擴容cluster的時候,是串行的遷移slot中的每一個key,這樣致使了
擴容的速度很是慢,百G的數據要數小時。擴容時間越長,越容易出現異常。數據庫

http://www.ithao123.cn/content-10677390.htmlvim

先有雞仍是先有蛋?

最近有朋友問了一個問題,說畢業後去大城市仍是小城市?去大公司仍是小公司?個人回答都是大城市!大公司!
爲何這麼說呢,你想一下,不管女孩男孩找朋友都喜歡找個子高胸大的。一樣的道理嘛,「大」總有大的好。
固然,若是你要有能力找一個胸大個子高就更完美了。後端

Redis 集羣簡介

Redis 是一個開源的 key-value 存儲系統,因爲出衆的性能,大部分互聯網企業都用來作服務器端緩存。Redis 在3.0版本前只支持單實例模式,雖然支持主從模式、哨兵模式部署來解決單點故障,可是如今互聯網企業動輒大幾百G的數據,可徹底是無法知足業務的需求,因此,Redis 在 3.0 版本之後就推出了集羣模式。

Redis 集羣採用了P2P的模式,徹底去中心化。Redis 把全部的 Key 分紅了 16384 個 slot,每一個 Redis 實例負責其中一部分 slot 。集羣中的全部信息(節點、端口、slot等),都經過節點之間按期的數據交換而更新。
Redis 客戶端能夠在任意一個 Redis 實例發出請求,若是所需數據不在該實例中,經過重定向命令引導客戶端訪問所需的實例。

隨隨便便搭建一個集羣

安裝部署任何一個應用其實都很簡單,只要安裝步驟一步一步來就好了。下面說一下 Redis 集羣搭建規劃,因爲集羣至少須要6個節點(3主3從模式),因此,沒有這麼多機器給我玩,我本地也起不了那麼多虛擬機(電腦太爛),如今計劃是在一臺機器上模擬一個集羣,固然,這和生產環境的集羣搭建沒本質區別。

我如今就要在已經有安裝了 Redis 的一個 CentOS 下開始進行集羣搭建,若是你還不是很清楚 Linux 下如何安裝 Redis ,能夠去看這一篇文章《瞭解一下 Redis 並在 CentOS 下進行安裝配置》。請注意,下面全部集羣搭建環境都基於已安裝好的 Redis 作的。

1.建立文件夾
咱們計劃集羣中 Redis 節點的端口號爲 9001-9006 ,端口號即集羣下各實例文件夾。數據存放在 端口號/data 文件夾中。

mkdir_9001-9006

mkdir /usr/local/redis-cluster
cd redis-cluster/
mkdir -p 9001/data 9002/data 9003/data 9004/data 9005/data 9006/data

 

2.複製執行腳本
在 /usr/local/redis-cluster 下建立 bin 文件夾,用來存放集羣運行腳本,並把安裝好的 Redis 的 src 路徑下的運行腳本拷貝過來。看命令:

mkdir redis-cluster/bin
cd /usr/local/redis/src
cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server redis-trib.rb /usr/local/redis-cluster/bin

3.複製一個新 Redis 實例
咱們如今從已安裝好的 Redis 中複製一個新的實例到 9001 文件夾,並修改 redis.conf 配置。

mv9001

cp /usr/local/redis/* /usr/local/redis-cluster/9001

 

注意,修改  配置和單點惟一區別是下圖部分,其他仍是常規的這幾項:redis.conf
port 9001(每一個節點的端口號)
daemonize yes
bind 192.168.119.131(綁定當前機器 IP)
dir /usr/local/redis-cluster/9001/data/(數據文件存放位置)
pidfile /var/run/redis_9001.pid(pid 9001和port要對應)
cluster-enabled yes(啓動集羣模式)
cluster-config-file nodes9001.conf(9001和port要對應)
cluster-node-timeout 15000
appendonly yes
 

集羣搭建配置重點就是取消下圖中的這三個配置:
cluster_conf

4.再複製出五個新 Redis 實例
咱們已經完成了一個節點了,其實接下來就是機械化的再完成另外五個節點,其實能夠這麼作:把 9001 實例 複製到另外五個文件夾中,惟一要修改的就是 redis.conf 中的全部和端口的相關的信息便可,其實就那麼四個位置。開始操做,看圖:

cp9001-9006

\cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9002
\cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9003
\cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9004
\cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9005
\cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9006

\cp -rf 命令是不使用別名來複制,由於 cp 實際上是別名 cp -i,操做時會有交互式確認,比較煩人。

5.修改 9002-9006 的 redis.conf 文件
其實很是簡單了,你經過搜索會發現其實只有四個點須要修改,咱們全局替換下吧,進入相應的節點文件夾,作替換就行了。命令很是簡單,看圖:

%s-9001-9002g

vim redis.conf
:%s/9001/9002g

回車後,就會有替換幾個地方成功的提示,不放心能夠手工檢查下:

%s-success

其實咱們也就是替換了下面這四行:

port 9002
dir /usr/local/redis-cluster/9002/data/
cluster-config-file nodes-9002.conf
pidfile /var/run/redis_9002.pid

到這裏,咱們已經把最基本的環境搞定了,接下來就是啓動了。

其實咱們已經幾乎搭建好了

1.啓動 9001-9006 六個節點
少廢話,直接看圖:
redis-server_start

/usr/local/bin/redis-server /usr/local/redis-cluster/9001/redis.conf 
/usr/local/bin/redis-server /usr/local/redis-cluster/9002/redis.conf 
/usr/local/bin/redis-server /usr/local/redis-cluster/9003/redis.conf 
/usr/local/bin/redis-server /usr/local/redis-cluster/9004/redis.conf 
/usr/local/bin/redis-server /usr/local/redis-cluster/9005/redis.conf 
/usr/local/bin/redis-server /usr/local/redis-cluster/9006/redis.conf

 

能夠檢查一下是否啓動成功:ps -el | grep redis
看的出來,六個節點已經所有啓動成功了。

2.隨便找一個節點測試試

/usr/local/redis-cluster/bin/redis-cli -h 192.168.119.131 -p 9001

set name mafly

 

redis-server_start_test

鏈接成功了,但好像報錯了阿???
(error) CLUSTERDOWN Hash slot not served(不提供集羣的散列槽),這是什麼鬼?
這是由於雖然咱們配置並啓動了 Redis 集羣服務,可是他們暫時還並不在一個集羣中,互相直接發現不了,並且尚未可存儲的位置,就是所謂的slot(槽)

3.安裝集羣所需軟件
因爲 Redis 集羣須要使用 ruby 命令,因此咱們須要安裝 ruby 和相關接口。

yum install ruby
yum install rubygems
gem install redis 

 

yum_ruby

這纔是真正的建立集羣

先不廢話,直接敲命令:

/usr/local/redis-cluster/bin/redis-trib.rb create --replicas 1 192.168.119.131:9001 192.168.119.131:9002 192.168.119.131:9003 192.168.119.131:9004 192.168.119.131:9005 192.168.119.131:9006

cluster_create

簡單解釋一下這個命令:調用 ruby 命令來進行建立集羣,--replicas 1 表示主從複製比例爲 1:1,即一個主節點對應一個從節點;而後,默認給咱們分配好了每一個主節點和對應從節點服務,以及 solt 的大小,由於在 Redis 集羣中有且僅有 16383 個 solt ,默認狀況會給咱們平均分配,固然你能夠指定,後續的增減節點也能夠從新分配。

M: 10222dee93f6a1700ede9f5424fccd6be0b2fb73 爲主節點Id

S: 9ce697e49f47fec47b3dc290042f3cc141ce5aeb 192.168.119.131:9004 replicates 10222dee93f6a1700ede9f5424fccd6be0b2fb73 從節點下對應主節點Id

目前來看,9001-9003 爲主節點,9004-9006 爲從節點,並向你確認是否贊成這麼配置。輸入 yes 後,會開始集羣建立。

cluster_create_success

上圖則表明集羣搭建成功啦!!!

驗證一下:
依然是經過客戶端命令鏈接上,經過集羣命令看一下狀態和節點信息等。

/usr/local/redis-cluster/bin/redis-cli -c -h 192.168.119.131 -p 9001 cluster info cluster nodes

cluster_info

經過命令,能夠詳細的看出集羣信息和各個節點狀態,主從信息以及鏈接數、槽信息等。這麼看到,咱們已經真的把 Redis 集羣搭建部署成功啦!

設置一個 mafly:
你會發現,當咱們 set name mafly 時,出現了 Redirected to slot 信息並自動鏈接到了9002節點。這也是集羣的一個數據分配特性,這裏不詳細說了。

redirected_9002

總結一下

這一篇 Redis 集羣部署搭建的文章真的是一步一步的走下來的,只要你安裝個人步驟來,就保證你能成功搭建一個 Redis 集羣玩玩,也能夠這麼說,除了步驟繁瑣外,幾乎不存在技術含量,估計能看完的人都感受累(說真的,寫這種文章真的很累人)。

接下來可能就是動態擴容、增長節點和減小節點,從新分配槽大小等,固然,還有最重要的就是怎麼和咱們程序結合起來,以及如何更好的把 Redis 緩存集羣發揮出應有的效果,這些纔是最重要的。

http://www.cnblogs.com/mafly/p/redis_cluster.html

 

 

redis3.0 cluster功能介紹

redis從3.0開始支持集羣功能。redis集羣採用無中心節點方式實現,無需proxy代理,客戶端直接與redis集羣的每一個節點鏈接,根據一樣的hash算法計算出key對應的slot,而後直接在slot對應的redis上執行命令。在redis看來,響應時間是最苛刻的條件,增長一層帶來的開銷是redis不緣由接受的。所以,redis實現了客戶端對節點的直接訪問,爲了去中心化,節點之間經過gossip協議交換互相的狀態,以及探測新加入的節點信息。redis集羣支持動態加入節點,動態遷移slot,以及自動故障轉移。

集羣架構
每一個節點都會跟其餘節點保持鏈接,用來交換彼此的信息。節點組成集羣的方式使用cluster meet命令,meet命令可讓兩個節點相互握手,而後經過gossip協議交換信息。若是一個節點r1在集羣中,新節點r2加入的時候與r1節點握手,r1節點會把集羣內的其餘節點信息經過gossip協議發送給r2,r2會一一與這些節點完成握手,從而加入到集羣中。
節點在啓動的時候會生成一個全局的標識符,並持久化到配置文件,在節點與其餘節點握手後,這些信息也都持久化下來。節點與其餘節點通訊,標識符是它惟一的標識,而不是IP、PORT地址。若是一個節點移動位置致使IP、PORT地址發生變動,集羣內的其餘節點能把該節點的IP、PORT地址糾正過來。

集羣數據分佈
集羣數據以數據分佈表的方式保存在各個slot上。默認的數據分佈表默認含有16384個slot。
key與slot映射使用的CRC16算法,即:slot = CRC16(key) mod 16384。
集羣只有在16384個slot都有對應的節點才能正常工做。slot能夠動態的分配、刪除和遷移。
每一個節點會保存一份數據分佈表,節點會將本身的slot信息發送給其餘節點,發送的方式使用一個unsigned char的數組,數組長度爲16384/8。每一個bit標識爲0或者1來標識某個slot是不是它負責的。

使用這樣的方式傳輸,就能大大減小數據分佈表的字節。這種方式使用的字節爲2048,若是純粹的傳遞數據分佈表,那邊一個slot至少須要2字節的slot值+2字節port+4字節ip,共8*16384=131072字節,或者以ip、port爲鍵,把節點對應的slot放在後面,那麼也至少須要2*16384加上節點的數量,也遠遠大於2048字節。因爲節點間不停的在傳遞數據分佈表,因此爲了節省帶寬,redis選擇了只傳遞本身的分佈數據。但這樣的方式也會帶來管理方面的麻煩,若是一個節點刪除了本身負責的某個slot,這樣該節點傳遞給其餘節點數據分佈表的slot標識爲0,而redis採用了bitmapTestBit方法,只處理slot爲1的節點,而並未把每一個slot與收到的數據分佈表對比,從而產生了節點間數據分佈表視圖的不一致。這種問題目前只能經過使用者來避免。

redis目前還支持slot的遷移,能夠把一個slot從一個節點遷移到另外一個節點,節點上的數據須要使用者經過cluster getkeysinslot去除遷移slot上的key,而後執行migrate命令一個個遷移到新節點。具體細節會在下面的slot遷移章節介紹。

集羣訪問
客戶端在初始化的時候只須要知道一個節點的地址便可,客戶端會先嚐試向這個節點執行命令,好比「get key」,若是key所在的slot恰好在該節點上,則可以直接執行成功。若是slot不在該節點,則節點會返回MOVED錯誤,同時把該slot對應的節點告訴客戶端。客戶端能夠去該節點執行命令。目前客戶端有兩種作法獲取數據分佈表,一種就是客戶端每次根據返回的MOVED信息緩存一個slot對應的節點,可是這種作法在初期會常常形成訪問兩次集羣。還有一種作法是在節點返回MOVED信息後,經過cluster nodes命令獲取整個數據分佈表,這樣就能每次請求到正確的節點,一旦數據分佈表發生變化,請求到錯誤的節點,返回MOVED信息後,從新執行cluster nodes命令更新數據分佈表。

在訪問集羣的時候,節點可能會返回ASK錯誤。這種錯誤是在key對應的slot正在進行數據遷移時產生的,這時候向slot的原節點訪問,若是key在遷移源節點上,則該次命令能直接執行。若是key不在遷移源節點上,則會返回ASK錯誤,描述信息會附上遷移目的節點的地址。客戶端這時候要先向遷移目的節點發送ASKING命令,而後執行以前的命令。

這些細節通常都會被客戶端sdk封裝起來,使用者徹底感覺不到訪問的是集羣仍是單節點。

集羣支持hash tags功能,便可以把一類key定位到同一個節點,tag的標識目前支持配置,只能使用{},redis處理hash tag的邏輯也很簡單,redis只計算從第一次出現{,到第一次出現}的substring的hash值,substring爲空,則仍然計算整個key的值,這樣對於foo{}{bar}、{foo}{bar}、foo這些衝突的{},也能取出tag值。使用者需遵循redis的hash tag規範。

127.0.0.1:6379> CLUSTER KEYSLOT foo{hash_tag}
(integer) 2515
127.0.0.1:6379> CLUSTER KEYSLOT fooadfasdf{hash_tag}
(integer) 2515
127.0.0.1:6379> CLUSTER KEYSLOT fooadfasdfasdfadfasdf{hash_tag}
(integer) 2515
集羣版本的redis可以支持所有的單機版命令。不過仍是有些命令會有些限制。

訂閱在使用上與單機版沒有任何區別。訂閱功能經過集羣間共享publish消息實現的,客戶端能夠向任意一個節點(包括slave節點)訂閱消息,而後在一個節點上執行的publish命令,該節點會把該命令傳播給集羣內的每一個節點,這樣訂閱該消息的客戶端就能收到該命令。

redis集羣版只使用db0,select命令雖然可以支持select 0。其餘的db都會返回錯誤。
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> select 1
(error) ERR SELECT is not allowed in cluster mode
redis集羣版對多key命令的支持,只能支持多key都在同一個slot上,即便多個slot在一個節點上也不行。

127.0.0.1:6379> mget key7 key28
(error) CROSSSLOT Keys in request don't hash to the same slot
事務的支持只能在也一個slot上完成。MULTI命令以後的命令的key必須都在同一個slot上,若是某條命令的key對應不在相同的slot上,則事務直接回滾。遷移的時候,在遷移源節點執行命令的key必須在移原節點上存在,不然事務就會回滾。在遷移目的節點執行的時候須要先執行ASKING命令再執行MULTI命令,這樣接下來該slot的命令都能被執行。能夠看出,對於單key和相同hash tags的事務,集羣仍是能很好的支持。

在遷移的時候有個地方須要注意,對於多key命令在遷移目的節點執行時,若是多個key全在該節點上,則命令沒法執行。以下所示,key和key14939對應的slot爲12539,執行命令的節點是遷移目的節點:

127.0.0.1:6379> asking
OK
127.0.0.1:6379> mget key key14939
(error) TRYAGAIN Multiple keys request during rehashing of slot

集羣消息
集羣間互相發送消息,使用另外的端口,全部的消息在該端口上完成,能夠成爲消息總線,這樣能夠作到不影響客戶端訪問redis,可見redis對於性能的追求。目前集羣有以下幾種消息:

CLUSTERMSG_TYPE_PING:gossip協議的ping消息。
CLUSTERMSG_TYPE_PONG:gossip協議的pong消息。
CLUSTERMSG_TYPE_MEET:握手消息。
CLUSTERMSG_TYPE_FAIL:master節點檢測到,超過半數master認爲某master離線,則發送fail消息。
CLUSTERMSG_TYPE_PUBLISH:publish消息,向其餘節點推送消息。
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST:故障轉移時,slave發送向其餘master投票請求。
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK:故障轉移時,其餘master迴應slave的請求。
CLUSTERMSG_TYPE_UPDATE:通知某節點,它負責的某些slot被另外一個節點替換。
CLUSTERMSG_TYPE_MFSTART:手動故障轉移時,slave請求master中止訪問,從而對比二者的數據偏移量,能夠達到一致。
集羣節點間相互通訊使用了gossip協議的push/pull方式,ping和pong消息,節點會把本身的詳細信息和已經和本身完成握手的3個節點地址發送給對方,詳細信息包括消息類型,集羣當前的epoch,節點本身的epoch,節點複製偏移量,節點名稱,節點數據分佈表,節點master的名稱,節點地址,節點flag爲,節點所處的集羣狀態。節點根據本身的epoch和對方的epoch來決定哪些數據須要更新,哪些數據須要告訴對方更新。而後根據對方發送的其餘地址信息,來發現新節點的加入,從而和新節點完成握手。

節點默認每秒在集羣中的其餘節點選擇一個節點,發送ping消息。選擇節點步驟是:

隨機 5 個節點。
跳過斷開鏈接和已經在ping還沒收到pong響應的節點。
從篩選的節點中選擇最近一次接收pong回覆距離如今最舊的節點。
除了常規的選擇節點外,對於那些一直未隨機到節點,redis也有所支持。當有節點距離上一次接收到pong消息超過節點超時配置的一半,節點就會給這些節點發送ping消息。

ping消息會帶上其餘節點的信息,選擇其餘節點步驟是:

最多選擇3個節點。
最多隨機遍歷6個節點,若是由於一些條件不能被選出,可能會不滿3個。
忽略本身。
忽略正在握手的節點。
忽略帶有 NOADDR 標識的節點。
忽略鏈接斷開並且沒有負責任何slot的節點。
ping消息會把發送節點的ping_sent改爲當前時間,直到接收到pong消息,才更新ping_sent爲0。當ping消息發送後,超過節點超時配置的一半,就會把發送節點的鏈接斷開。超過節點超時配置,就會認爲該節點已經下線。

接收到某個節點發來的ping或者pong消息,節點會更新對接收節點的認識。好比該節點主從角色是否變化,該節點負責的slot是否變化,而後獲取消息帶上的節點信息,處理新節點的加入。

slot更新這裏要細說下。假設這裏是節點A接收到節點B的消息。A節點會取出保存的B節點的分佈表與消息的分佈表進行對比,B節點若是是slave,則比較的是A節點保存的B的master的分佈表和消息中的分佈表。比較只是使用memcmp簡單的比較兩份數據是否同樣,同樣則無需處理,不同就處理不一致的slot。更新的時候,這裏只是處理消息中分佈表爲1的slot。若是和A節點保持一致或者該slot正準備遷移到A節點,則繼續處理。若是slot產生了衝突,則以epoch大的爲準。若是衝突的slot中有A本身負責的節點,並且B比A的epoch大致使須要更新slot爲B負責,此時A負責的slot爲0的時候,能夠認爲B是A的slave。這種狀況常常發生在A由原來的master變成slave,B提高爲master的場景下。

前面說到slot更新的時候,若是B比A的epoch大,則A更新對slot的認識。若是A比B的epoch大, 在redis接下來的邏輯會再次處理,A會給B發送update消息,B收到A發送的update消息,執行slot更新方法。這種狀況也常常發生在主從切換的時候。第一種狀況發生在新master把數據分佈表推給舊master。第二種狀況發生在舊master給新master發消息的時候,新master給舊master發送update消息。

slot遷移
redis cluster支持slot的動態遷移,遷移須要按照指定步驟進行,否則可能破壞當前的集羣數據分佈表。cluster setslot <slot> IMPORTING <node ID>命令在遷移目的節點執行,表示須要把該slot遷移到本節點。redis的cluster setslot命令提供了對遷移的支持。cluster setslot <slot> MIGRATING <node ID>命令在遷移源節點執行,表示須要把該slot遷出。cluster setslot <slot> NODE <node ID>在遷移完成後在遷移源節點和遷移目的節點執行後,表示遷移完成,數據分佈表恢復穩定。若是須要取消遷移操做,在遷移源節點和遷移目的節點上執行cluster setslot <slot> STABLE。

下面先來看一下完整的遷移流程:

在遷移目的節點執行cluster setslot <slot> IMPORTING <node ID>命令,指明須要遷移的slot和遷移源節點。
在遷移源節點執行cluster setslot <slot> MIGRATING <node ID>命令,指明須要遷移的slot和遷移目的節點。
在遷移源節點執行cluster getkeysinslot獲取該slot的key列表。
在遷移源節點執行對每一個key執行migrate命令,該命令會同步把該key遷移到目的節點。
在遷移源節點反覆執行cluster getkeysinslot命令,直到該slot的列表爲空。
在遷移源節點和目的節點執行cluster setslot <slot> NODE <node ID>,完成遷移操做。

遷移過程當中該slot容許繼續有流量進來,redis保證了遷移過程當中slot的正常訪問。在遷移過程當中,對於該slot的請求,若是key在源節點,則表示該key尚未遷移到目的節點。源節點會返回ASK錯誤,告訴客戶端去遷移目的節點請求。這樣新的key就直接寫入到遷移目的節點了。客戶端寫入目的節點前須要發送ASKING命令,告訴遷移目的節點我是寫入增量數據,沒有ASKING命令,遷移目的節點會不認此次請求,返回MOVED錯誤,告訴客戶端去遷移源節點請求。

憑藉ASK機制和migrate命令,redis能保證slot的全量數據和增量數據都能導入目的節點。由於對於源節點返回了ASK錯誤,就能保證該key不在源節點上,那麼它只會出如今目的節點或者不存在。因此客戶端獲取ASK錯誤到向目的節點無需保證原子性。而後migrate命令是個原子操做,它會等待目的節點寫入成功纔在源節點返回,保證了遷移期間不接受其餘請求,從一個外部客戶端的視角來看,在某個時間點上,遷移的鍵要麼存在於源節點,要麼存在於目的節點,但不會同時存在於源節點和目的節點。

遷移過程當中幾乎不影響用戶使用,除了在多key的命令在遷移目的節點沒法執行,這個在集羣訪問已經說明。

不過因爲migrate命令是同步阻塞執行的,因此若是key對應的value很大,會增長阻塞時間,特別對於list、set、zset等結構若是value很大的話,redis並不關心這些結構的長度,而是直接以key爲單位一次性遷移。同時遷移過程當中大量執行migrate命令,會增長客戶端的響應時間。

遷移的時候在master出現異常的時候,遷移工做須要作些處理。
若是在遷移過程當中,源節點宕機,此時需作以下調整:
目的節點執行cluster setslot <slot> IMPORTING <node ID>命令,node ID爲源節點group的新master
源節點group內的新master執行cluster setslot <slot> MIGRATING <node ID>命令,遷移該slot到目的節點。
這樣能夠繼續完成遷移操做。

若是在遷移過程當中,目的節點宕機,此時需作以下調整:
目的節點group內的新master執行cluster setslot <slot> IMPORTING <node ID>命令,node ID爲源節點。
源節點執行cluster setslot <slot> MIGRATING <node ID>命令,遷移該slot到目的節點group內的新master。
這樣也能夠繼續完成遷移操做。

主從複製
集羣間節點支持主從關係,複製的邏輯基本複用了單機版的實現。不過仍是有些地方須要注意。
首先集羣間節點創建主從關係再也不使用原有的SLAVEOF命令和SLAVEOF配置,而是經過cluster replicate命令,這保證了主從節點須要先完成握手,才能創建主從關係。
集羣是不能組成鏈式主從關係的,也就是說從節點不能有本身的從節點。不過對於集羣外的沒開啓集羣功能的節點,redis並不干預這些節點去複製集羣內的節點,可是在集羣故障轉移時,這些集羣外的節點,集羣不會處理。
集羣內節點想要複製另外一個節點,須要保證本節點再也不負責任何slot,否則redis也是不容許的。
集羣內的從節點在與其餘節點通訊的時候,傳遞的消息中數據分佈表和epoch是master的值。

故障轉移

集羣主節點出現故障,發生故障轉移時,其餘主節點會把故障主節點的從節點自動提爲主節點,原來的主節點恢復後,自動成爲新主節點的從節點。

這裏先說明,把一個master和它的所有slave描述爲一個group,故障轉移是以group爲單位的,集羣故障轉移的方式跟sentinel的實現很相似。某個節點一段時間沒收到心跳響應,則集羣內的master會把該節點標記爲pfail,相似sentinel的sdown。集羣間的節點會交換相互的認識,超過一半master認爲該異常master宕機,則這些master把異常master標記爲fail,相似sentinel的odown。fail消息會被master廣播出來。group的slave收到fail消息後開始競選成爲master。競選的方式跟sentinel選主的方式相似,都是使用了raft協議,slave會從其餘的master拉取選票,票數最多的slave被選爲新的master,新master會立刻給集羣內的其餘節點發送pong消息,告知本身角色的提高。其餘slave接着開始複製新master。等舊master上線後,發現新master的epoch高於本身,經過gossip消息交互,把本身變成了slave。大體就是這麼個流程。自動故障轉移的方式跟sentinel很像,

redis還支持手動的故障轉移,即經過在slave上執行cluster failover命令,可讓slave提高爲master。failover命令支持傳入FORCE和TAKEOVER參數。

FORCE:使用FORCE參數與sentinel的手動故障轉移流程基本相似,強制開始一次故障轉移。
不傳入額外參數:若是主節點異常,則不能進行failover,主節點正常的狀況下須要先比較從節點和主節點的偏移量,此時會讓主節點中止客戶端請求,直到超時或者故障轉移完成。主從偏移量相同後開始手動故障轉移流程。
TAKEOVER:這種手動故障轉移的方式比較暴力,slave直接提高本身的epoch爲最大的epoch。並把本身變成master。這樣在消息交互過程當中,舊master能發現本身的epoch小於該slave,同時二者負責的slot一致,它會把本身降級爲slave。

均衡集羣的slave(Replica migration)

在集羣運行過程當中,有的master的slave宕機,致使了該master成爲孤兒master(orphaned masters),而有的master有不少slave。此處孤兒master的定義是那些原本有slave,可是所有離線的master,對於那些原來就沒有slave的master不能認爲是孤兒master。redis集羣支持均衡slave功能,官方稱爲Replica migration,而我以爲均衡集羣的slave更好理解該概念。集羣能把某個slave較多的group上的slave遷移到那些孤兒master上,該功能經過cluster-migration-barrier參數配置,默認爲1。slave在每次定時任務都會檢查是否須要遷移slave,即把本身變成孤兒master的slave。 知足如下條件,slave就會成爲孤兒master的slave:

本身所在的group是slave最多的group。
目前存在孤兒master。
本身所在的group的slave數目至少超過2個,只有本身一個的話遷移到其餘group,本身原來的group的master又成了孤兒master。
本身所在的group的slave數量大於cluster-migration-barrier配置。
與group內的其餘slave基於memcmp比較node id,本身的node id最小。這個能夠防止多個slave併發複製孤兒master,從而原來的group失去過多的slave。

網絡分區說明

redis的集羣模式下,客戶端須要和所有的節點保持鏈接,這樣可能出現網絡分區問題,客戶端和一些節點在一個網絡分區,另外一部分節點在另外一個網絡分區。在分區期間,客戶端仍然能執行命令,直到集羣通過cluster-node-timeout發現分區狀況,節點探測到有slot沒法提供服務,纔開始禁止客戶端執行命令。

這時候會出現一種現象,假設客戶端和一個master在小分區,其餘節點在大分區。超時後,其餘節點共同投票把group內的一個slave提爲master,等分區恢復。舊的master會成爲新master的slave。這樣在cluster-node-timeout期間對舊master的寫入數據都會丟失。
這個問題能夠經過設置cluster-node-timeout來減小不一致。若是對一致性要求高的應用還能夠經過min-slaves-to-write配置來提升寫入的要求。

slot保存key列表

redis提供了cluster countkeysinslot和cluster getkeysinslot命令,能夠得到某個slot的所有key列表。經過該列表,能夠實現slot的遷移。該功能是經過skiplist實現的,skiplist是redis內部用來實現zset的數據結構,在slot保持key的時候也派上了用場。redis全部在db層對hash表的操做,也會在skiplist上執行相應的操做。好比往hash表增長數據,redis也會往skiplist也寫一份數據,該skiplist的score就是slot的值,value對應了具體的key。這等因而redis在數據分佈表上冗餘了全部的key。不過相比skiplist所帶來遷移的方便,冗餘的結果是能夠接受的,這也指望客戶端,不要使用過長的key,從而增長內存的消耗。

附錄1:集羣相關命令

cluster meet ip:port
集羣間相互握手,加入彼此所在的集羣。(將指定節點加入到集羣)

cluster nodes
獲取集羣間節點信息的列表,以下所示,格式爲<node ID> <node IP:PORT> <node role> [master node ID|-] <node ping_sent> <node pong_received> <node epoch> <node status>。

127.0.0.1:6379> cluster nodes
a15705fdb7cac60e07ff699bf4c514e80f245a2c 10.180.157.205:6379 slave 2b5603326d0fca28031467727fae4558115a99d8 0 1450854214289 11 connected
6477541e4594e60e095c8f440882636236545936 10.180.157.202:6379 slave 9b35a393fa6623887215023b761d531dde452d3c 0 1450854211276 12 connected
ecf9ae60e87ea3358d9c5f1f269e0ed9a387ea40 10.180.157.201:6379 master - 0 1450854214788 5 connected 10923-16383
2b5603326d0fca28031467727fae4558115a99d8 10.180.157.200:6379 master - 0 1450854213283 11 connected 5461-10922
f31f6ce49b3a2f3a246b2d97349c8f8614cf3a2c 10.180.157.208:6379 slave ecf9ae60e87ea3358d9c5f1f269e0ed9a387ea40 0 1450854212286 9 connected
9b35a393fa6623887215023b761d531dde452d3c 10.180.157.199:6379 myself,master - 0 0 12 connected 0-5460

cluster myid
返回節點的id。
127.0.0.1:6379> cluster myid
"9b35a393fa6623887215023b761d531dde452d3c"

cluster slots
返回集羣間節點負責的數據分佈表。
127.0.0.1:6379> cluster slots
1) 1) (integer) 10923
2) (integer) 16383
3) 1) "10.180.157.201"
2) (integer) 6379
4) 1) "10.180.157.208"
2) (integer) 6379
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "10.180.157.200"
2) (integer) 6379
4) 1) "10.180.157.205"
2) (integer) 6379
3) 1) (integer) 0
2) (integer) 5460
3) 1) "10.180.157.199"
2) (integer) 6379
4) 1) "10.180.157.202"
2) (integer) 6379

cluster flushslots
清空該節點負責slots,必須在節點負責的這些slot都沒有數據的狀況下才能執行,該命令須要謹慎使用,因爲以前說的bitmapTestBit方法,redis只比較負責的節點,清空的slots信息沒法被其餘節點同步。

cluster addslots [slot]
在當前節點上增長slot。(將指定的一個或多個slot 指派給當前節點)

cluster delslots [slot]
在節點上取消slot的負責。這也會致使前面說的slot信息沒法同步,並且一旦集羣有slot不負責,配置cluster-require-full-coverage爲yes的話,該節點就沒法提供服務了,因此使用也需謹慎。

cluster setslot <slot> MIGRATING <nodeid>
把本節點負責的某個slot設置爲遷移到目的節點。(即將本節點的slot指派給或叫遷移到指定的節點)

cluster setslot <slot> IMPORTING <nodeid>
設置某個slot爲從遷移源節點遷移標誌。(即將指定節點的slot指派給或叫遷移到本節點)

cluster setslot <slot> STABLE
設置某個slot爲從遷移狀態恢復爲正常狀態。(取消slot的導入(importing)或遷移(migrating))

cluster setslot <slot> NODE <nodeid>
設置某個slot爲某節點負責。該命令使用也須要注意,cluster setslot的四個命令須要配置遷移工具使用,單獨使用容易引發集羣混亂。該命令在集羣出現異常時,須要指定某個slot爲某個節點負責時,最好在每一個節點上都執行一遍,至少要在遷移的節點和最高epoch的節點上執行成功。(將指定的slot指派給指定的節點,若是該slot已經指派給另外一個節點,則要另外一個節點先刪除該slot)

cluster info
集羣的一些info信息。
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:12
cluster_my_epoch:12
cluster_stats_messages_sent:1449982
cluster_stats_messages_received:1182698

cluster saveconfig
保存集羣的配置文件,集羣默認在配置修改的時候會自動保存配置文件,該方法也能手動執行命令保存。

cluster keyslot
能夠查詢某個key對應的slot地址。
127.0.0.1:6379> cluster keyslot key
(integer) 12539

cluster countkeysinslot
能夠查詢該節點負責的某個slot內部key的數量。
127.0.0.1:6379> cluster countkeysinslot 13252
(integer) 2

cluster getkeysinslot <slot> <count>
能夠查詢該節點負責的某個slot內部指定數量的key列表。
127.0.0.1:6379> cluster getkeysinslot 13252 10
1) "key0"
2) "key2298"

cluster forget
把某個節點加入黑名單,這樣就沒法完成握手。黑名單的過時時爲60s,60s後兩節點又會繼續完成握手。

cluster replicate <nodeid>
負責某個節點,成爲它的slave。(將當前節點設置爲指定nodeid節點的從節點)

cluster slaves
列出某個節點slave列表。
127.0.0.1:6379> cluster slaves 2b5603326d0fca28031467727fae4558115a99d8
1) "a15705fdb7cac60e07ff699bf4c514e80f245a2c 10.180.157.205:6379 slave 2b5603326d0fca28031467727fae4558115a99d8 0 1450854932667 11 connected"

cluster count-failure-reports
列出某個節點的故障轉移記錄的長度。

cluster failover [FORCE|TAKEOVER]
手動執行故障轉移。

cluster set-config-epoch
設置節點epoch,只有在節點加入集羣前才能設置。

cluster reset [SOFT|HARD]
重置集羣信息,soft是清空其餘節點的信息,但不修改本身的id。hard還會修改本身的id。不傳該參數則使用soft方式。

readonly
在slave上執行,執行該命令後,能夠在slave上執行只讀命令。

readwrite
在slave上執行,執行該命令後,取消在slave上執行命令。

附錄2:集羣相關配置
cluster-enabled
說明:集羣開關,默認是不開啓集羣模式。
默認值:no。
是否能夠動態修改:no。
值的範圍:yes|no。

cluster-config-file
說明:集羣配置文件的名稱,每一個節點都有一個集羣相關的配置文件,持久化保存集羣的信息。
默認值:」nodes.conf」。
是否能夠動態修改:no。
值的範圍:文件路徑。

cluster-node-timeout
說明:節點的超時時間,單位是毫秒。
默認值:15000。
是否能夠動態修改:yes。
值的範圍:大於0。

cluster-slave-validity-factor
說明:在進行故障轉移的時候,group的所有slave都會請求申請爲master,可是有些slave可能與master斷開鏈接一段時間了,致使數據過於陳舊,這樣的slave不該該被提高爲master。該參數就是用來判斷slave節點與master斷線的時間是否過長。判斷方法是比較slave斷開鏈接的時間和(node-timeout * slave-validity-factor) + repl-ping-slave-period。
默認值:10。
是否能夠動態修改:yes。
值的範圍:大於等於0。

cluster-migration-barrier
說明:master的slave數量大於該值,slave才能遷移到其餘孤兒master上,具體說明見均衡集羣的slave章節。
默認值:1。
是否能夠動態修改:yes。
值的範圍:大於等於0。

cluster-require-full-coverage
說明:默認狀況下,集羣所有的slot有節點負責,集羣狀態才爲ok,才能提供服務。設置爲no,能夠在slot沒有所有分配的時候提供服務。不建議打開該配置,這樣會形成分區的時候,小分區的master一直在接受寫請求,而形成很長時間數據不一致。
默認值:yes。
是否能夠動態修改:yes。
值的範圍:yes|no。

 

 

 

 

1.Redis Cluster總覽

1.1 設計原則和初衷

官方文檔Cluster Spec中,做者詳細介紹了Redis集羣爲何要設計成如今的樣子。最核心的目標有三個:

  1. 性能:這是Redis賴以生存的看家本領,增長集羣功能後固然不能對性能產生太大影響,因此Redis採起了P2P而非Proxy方式、異步複製、客戶端重定向等設計,而犧牲了部分的一致性、使用性。
  2. 水平擴展:集羣的最重要能力固然是擴展,文檔中稱能夠線性擴展到1000結點。
  3. 可用性:在Cluster推出以前,可用性要靠Sentinel保證。有了集羣以後也自動具備了Sentinel的監控和自動Failover能力。

1.2 架構變化與CAP理論

Redis Cluster集羣功能推出已經有一段時間了。在單機版的Redis中,每一個Master之間是沒有任何通訊的,因此咱們通常在Jedis客戶端或者Codis這樣的代理中作Pre-sharding。按照CAP理論來講,單機版的Redis屬於保證CP(Consistency & Partition-Tolerancy)而犧牲A(Availability),也就說Redis可以保證全部用戶看到相同的數據(一致性,由於Redis不自動冗餘數據)和網絡通訊出問題時,暫時隔離開的子系統能繼續運行(分區容忍性,由於Master之間沒有直接關係,不須要通訊),可是不保證某些結點故障時,全部請求都能被響應(可用性,某個Master結點掛了的話,那麼它上面分片的數據就沒法訪問了)。

有了Cluster功能後,Redis從一個單純的NoSQL內存數據庫變成了分佈式NoSQL數據庫,CAP模型也從CP變成了AP。也就是說,經過自動分片和冗餘數據,Redis具備了真正的分佈式能力,某個結點掛了的話,由於數據在其餘結點上有備份,因此其餘結點頂上來就能夠繼續提供服務,保證了Availability。然而,也正由於這一點,Redis沒法保證曾經的強一致性了。這也是CAP理論要求的,三者只能取其二。

關於CAP理論的通俗講解,請參考個人譯文《多是CAP理論的最好解釋 》。簡單分析了Redis在架構上的變化後,我們就一塊兒來體驗一下Redis Cluster功能吧!


2.Redis集羣初探

Redis的安裝很簡單,之前已經介紹過,就不詳細說了。關於Redis Cluster的基礎知識以前也有過整理,請參考《Redis集羣功能預覽》。若是須要全面的瞭解,那必定要看官方文檔Cluster Tutorial,只看這一個就夠了!

2.1 集羣配置

要想開啓Redis Cluster模式,有幾項配置是必須的。此外爲了方便使用和後續的測試,我還額外作了一些配置:

  • 綁定地址:bind 192.168.XXX.XXX。不能綁定到127.0.0.1或localhost,不然指導客戶端重定向時會報」Connection refused」的錯誤。
  • 開啓Cluster:cluster-enabled yes
  • 集羣配置文件:cluster-config-file nodes-7000.conf。這個配置文件不是要咱們去配的,而是Redis運行時保存配置的文件,因此咱們也不能夠修改這個文件。
  • 集羣超時時間:cluster-node-timeout 15000。結點超時多久則認爲它宕機了。
  • 槽是否全覆蓋:cluster-require-full-coverage no。默認是yes,只要有結點宕機致使16384個槽沒全被覆蓋,整個集羣就所有中止服務,因此必定要改成no
  • 後臺運行:daemonize yes
  • 輸出日誌:logfile 「./redis.log」
  • 監聽端口:port 7000

配置好後,根據咱們的集羣規模,拷貝出來幾份一樣的配置文件,惟一不一樣的就是監聽端口,能夠依次改成700一、7002… 由於Redis Cluster若是數據冗餘是1的話,至少要3個Master和3個Slave,因此咱們拷貝出6個實例的配置文件。爲了不相互影響,爲6個實例的配置文件創建獨立的文件夾。

[root@8gVm redis-3.0.4]# pwd /root/Software/redis-3.0.4 [root@8gVm redis-3.0.4]# tree -I "*log|nodes*" cfg-cluster/ cfg-cluster/ ├── 7000 │ └── redis.conf.7000 ├── 7001 │ └── redis.conf.7001 ├── 7002 │ └── redis.conf.7002 ├── 7003 │ └── redis.conf.7003 ├── 7004 │ └── redis.conf.7004 └── 7005 └── redis.conf.7005 6 directories, 6 files

2.2 redis-trib管理器

Redis做者應該是個Ruby愛好者,Ruby客戶端就是他開發的。此次集羣的管理功能沒有嵌入到Redis代碼中,因而做者又順手寫了個叫作redis-trib的管理腳本。redis-trib依賴Ruby和RubyGems,以及redis擴展。能夠先用which命令查看是否已安裝ruby和rubygems,用gem list –local查看本地是否已安裝redis擴展。

最簡便的方法就是用apt或yum包管理器安裝RubyGems後執行gem install redis。若是網絡或環境受限的話,能夠手動安裝RubyGems和redis擴展(國外連接可能沒法下載,能夠從CSDN下載):

[root@8gVm Software]# wget https://github.com/rubygems/rubygems/releases/download/v2.2.3/rubygems-2.2.3.tgz [root@8gVm Software]# tar xzvf rubygems-2.2.3.tgz [root@8gVm Software]# cd rubygems-2.2.3 [root@8gVm rubygems-2.2.3]# ruby setup.rb --no-rdoc --no-ri [root@8gVm Software]# wget https://rubygems.org/downloads/redis-3.2.1.gem [root@8gVm Software]# gem install redis-3.2.1.gem --local --no-rdoc --no-ri Successfully installed redis-3.2.1 1 gem installed

2.3 集羣創建

首先,啓動咱們配置好的6個Redis實例。

[root@8gVm redis-3.0.4]# for ((i=0; i<6; ++i)) > do > cd cfg-cluster/700$i && ../../src/redis-server redis.conf.700$i && cd - > done

此時6個實例尚未造成集羣,如今用redis-trb.rb管理腳本創建起集羣。能夠看到,redis-trib默認用前3個實例做爲Master,後3個做爲Slave。由於Redis基於Master-Slave作數據備份,而非像Cassandra或Hazelcast同樣不區分結點角色,自動複製並分配Slot的位置到各個結點

[root@8gVm redis-3.0.4]# src/redis-trib.rb create --replicas 1 192.168.1.100:7000 192.168.1.100:7001 192.168.1.100:7002 192.168.1.100:7003 192.168.1.100:7004 192.168.1.100:7005 >>> Creating cluster Connecting to node 192.168.1.100:7000: OK Connecting to node 192.168.1.100:7001: OK Connecting to node 192.168.1.100:7002: OK Connecting to node 192.168.1.100:7003: OK Connecting to node 192.168.1.100:7004: OK Connecting to node 192.168.1.100:7005: OK >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 192.168.1.100:7000 192.168.1.100:7001 192.168.1.100:7002 Adding replica 192.168.1.100:7003 to 192.168.1.100:7000 Adding replica 192.168.1.100:7004 to 192.168.1.100:7001 Adding replica 192.168.1.100:7005 to 192.168.1.100:7002 ... Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join.... >>> Performing Cluster Check (using node 192.168.1.100:7000) ... [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.

至此,集羣就已經創建成功了!「貼心」的Redis還在utils/create-cluster下提供了一個create-cluster腳本,可以建立出一個集羣,相似咱們上面創建起的3主3從的集羣。

2.4 簡單測試

咱們鏈接到集羣中的任意一個結點,啓動redis-cli時要加-c選項,存取兩個Key-Value感覺一下Redis久違的集羣功能。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 192.168.1.100:7000> set foo bar -> Redirected to slot [12182] located at 192.168.1.100:7002 OK 192.168.1.100:7002> set hello world -> Redirected to slot [866] located at 192.168.1.100:7000 OK 192.168.1.100:7000> get foo -> Redirected to slot [12182] located at 192.168.1.100:7002 "bar" 192.168.1.100:7002> get hello -> Redirected to slot [866] located at 192.168.1.100:7000 "world"

仔細觀察可以注意到,redis-cli根據指示,不斷在7000和7002結點以前重定向跳轉。若是啓動時不加-c選項的話,就能看到以錯誤形式顯示出的MOVED重定向消息。

[root@8gVm redis-3.0.4]# src/redis-cli -h 192.168.1.100 -p 7000 192.168.1.100:7000> get foo (error) MOVED 12182 192.168.1.100:7002

2.5 集羣重啓

目前redis-trib的功能還比較弱,須要重啓集羣的話先手動kill掉各個進程,而後從新啓動就能夠了。這也有點太… 網上有人重啓後會碰到問題,我還比較幸運,這種「土鱉」的方式重啓試了兩次還沒發現問題。

[root@8gVm redis-3.0.4]# ps -ef | grep redis | awk '{print $2}' | xargs kill

 


3.高級功能嚐鮮

說是「高級功能」,其實在其餘分佈式系統中早就都有實現了,只不過在Redis世界裏是比較新鮮的。本部分主要試驗一下Redis Cluster中的數據遷移(Resharding)和故障轉移功能。

3.1 數據遷移

本小節咱們體驗一下Redis集羣的Resharding功能!

3.1.1 建立測試數據

首先保存foo1~10共10個Key-Value做爲測試數據。

[root@8gVm redis-3.0.4]# for ((i=0; i<10; ++i)) > do > src/redis-cli -c -h 192.168.1.100 -p 7000 set foo$i bar > done [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 192.168.1.100:7000> keys * 1) "foo6" 2) "foo7" 3) "foo3" 4) "foo2" 192.168.1.100:7000> get foo4 -> Redirected to slot [9426] located at 192.168.1.100:7001 "bar" 192.168.1.100:7001> keys * 1) "foo4" 2) "foo8" 192.168.1.100:7001> get foo5 -> Redirected to slot [13555] located at 192.168.1.100:7002 "bar" 192.168.1.100:7002> keys * 1) "foo5" 2) "foo1" 3) "foo10" 4) "foo9"

3.1.2 啓動新結點

參照以前的方法新拷貝出兩份redis.conf配置文件redis.conf.7010和7011,與以前結點的配置文件作一下區分。啓動新的兩個Redis實例以後,經過redis-trib.rb腳本添加新的Master和Slave到集羣中。

[root@8gVm redis-3.0.4]# cd cfg-cluster/7010 && ../../src/redis-server redis.conf.7010 && cd - [root@8gVm redis-3.0.4]# cd cfg-cluster/7011 && ../../src/redis-server redis.conf.7011 && cd -

3.1.3 添加到集羣

使用redis-trib.rb add-node分別將兩個新結點添加到集羣中,一個做爲Master,一個做爲其Slave。

[root@8gVm redis-3.0.4]# src/redis-trib.rb add-node 192.168.1.100:7010 192.168.1.100:7000 >>> Adding node 192.168.1.100:7010 to cluster 192.168.1.100:7000 Connecting to node 192.168.1.100:7000: OK Connecting to node 192.168.1.100:7001: OK Connecting to node 192.168.1.100:7002: OK Connecting to node 192.168.1.100:7005: OK Connecting to node 192.168.1.100:7003: OK Connecting to node 192.168.1.100:7004: OK >>> Performing Cluster Check (using node 192.168.1.100:7000) ... [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. Connecting to node 192.168.1.100:7010: OK >>> Send CLUSTER MEET to node 192.168.1.100:7010 to make it join the cluster. [OK] New node added correctly. [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master - 0 1442452249525 0 connected ... [root@8gVm redis-3.0.4]# src/redis-trib.rb add-node --slave --master-id 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7011 192.168.1.100:7000 >>> Adding node 192.168.1.100:7011 to cluster 192.168.1.100:7000 Connecting to node 192.168.1.100:7000: OK Connecting to node 192.168.1.100:7010: OK Connecting to node 192.168.1.100:7001: OK Connecting to node 192.168.1.100:7002: OK Connecting to node 192.168.1.100:7005: OK Connecting to node 192.168.1.100:7003: OK Connecting to node 192.168.1.100:7004: OK >>> Performing Cluster Check (using node 192.168.1.100:7000) ... [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. Connecting to node 192.168.1.100:7011: OK >>> Send CLUSTER MEET to node 192.168.1.100:7011 to make it join the cluster. Waiting for the cluster to join. >>> Configure node as replica of 192.168.1.100:7010. [OK] New node added correctly.

3.1.4 Resharding

經過redis-trib.rb reshard能夠交互式地遷移Slot。下面的例子將5000個Slot從7000~7002遷移到7010上。也能夠經過./redis-trib.rb reshard <host>:<port> --from <node-id> --to <node-id> --slots --yes在程序中自動完成遷移。

[root@8gVm redis-3.0.4]# src/redis-trib.rb reshard 192.168.1.100:7000 Connecting to node 192.168.1.100:7000: OK Connecting to node 192.168.1.100:7010: OK Connecting to node 192.168.1.100:7001: OK Connecting to node 192.168.1.100:7002: OK Connecting to node 192.168.1.100:7005: OK Connecting to node 192.168.1.100:7011: OK Connecting to node 192.168.1.100:7003: OK Connecting to node 192.168.1.100:7004: OK >>> Performing Cluster Check (using node 192.168.1.100:7000) M: b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 slots:0-5460 (4128 slots) master 1 additional replica(s) M: 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 slots:0 (4000 slots) master 1 additional replica(s) ... [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 5000 What is the receiving node ID? 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1:all [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master - 0 1442455872019 7 connected 0-1332 5461-6794 10923-12255 b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 myself,master - 0 0 1 connected 1333-5460 b5ab302f5c2395e3c8194c354a85d02f89bace62 192.168.1.100:7001 master - 0 1442455875022 2 connected 6795-10922 0c565e207ce3118470fd5ed3c806eb78f1fdfc01 192.168.1.100:7002 master - 0 1442455874521 3 connected 12256-16383 ...

 

遷移完成後,查看以前保存的foo1~10的分佈狀況,能夠看到部分Key已經遷移到了新的結點7010上。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 keys "*" 1) "foo3" 2) "foo7" [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7001 keys "*" 1) "foo4" 2) "foo8" 3) "foo0" [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7002 keys "*" 1) "foo1" 2) "foo9" 3) "foo5" [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7010 keys "*" 1) "foo6" 2) "foo2"

3.2 故障轉移

在高可用性方面,Redis可算是可以」Auto」一把了!Redis Cluster重用了Sentinel的代碼邏輯,不須要單獨啓動一個Sentinel集羣,Redis Cluster自己就能自動進行Master選舉和Failover切換

下面咱們故意kill掉7010結點,以後能夠看到結點狀態變成了fail,而Slave 7011被選舉爲新的Master。

[root@8gVm redis-3.0.4]# kill 43637 [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes 0d1f9c979684e0bffc8230c7bb6c7c0d37d8a5a9 192.168.1.100:7010 master,fail - 1442456829380 1442456825674 7 disconnected b2036adda128b2eeffa36c3a2056444d23b548a8 192.168.1.100:7000 myself,master - 0 0 1 connected 1333-5460 b5ab302f5c2395e3c8194c354a85d02f89bace62 192.168.1.100:7001 master - 0 1442456848722 2 connected 6795-10922 0c565e207ce3118470fd5ed3c806eb78f1fdfc01 192.168.1.100:7002 master - 0 1442456846717 3 connected 12256-16383 5a3c67248b1df554fbf2c93112ba429f31b1d3d1 192.168.1.100:7005 slave 0c565e207ce3118470fd5ed3c806eb78f1fdfc01 0 1442456847720 6 connected 99bff22b97119cf158d225c2b450732a1c0d3c44 192.168.1.100:7011 master - 0 1442456849725 8 connected 0-1332 5461-6794 10923-12255 cd305d509c34842a8047e19239b64df94c13cb96 192.168.1.100:7003 slave b2036adda128b2eeffa36c3a2056444d23b548a8 0 1442456848220 4 connected 64b544cdd75c1ce395fb9d0af024b7f2b77213a3 192.168.1.100:7004 slave b5ab302f5c2395e3c8194c354a85d02f89bace62 0 1442456845715 5 connected

 

嘗試查詢以前保存在7010上的Key,能夠看到7011頂替上來繼續提供服務,整個集羣沒有受到影響。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 get foo6 "bar" [root@8gVm redis-3.0.4]# [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 get foo2 "bar"

4.內部原理剖析

前面咱們已經學習過,用Redis提供的redis-trib或create-cluster腳本能幾步甚至一步就創建起一個Redis集羣。這一部分咱們爲了深刻學習,因此要暫時拋開這些方便的工具,徹底手動創建一遍上面的3主3從集羣。

4.1 集羣發現:MEET

最開始時,每一個Redis實例本身是一個集羣,咱們經過cluster meet讓各個結點互相「握手」。這也是Redis Cluster目前的一個欠缺之處:缺乏結點的自動發現功能

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c :7000 myself,master - 0 0 0 connected [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster meet 192.168.1.100 7001 OK ... [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster meet 192.168.1.100 7005 OK [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes 7b953ec26bbdbf67179e5d37e3cf91626774e96f 192.168.1.100:7003 master - 0 1442466369259 4 connected 5d9f14cec1f731b6477c1e1055cecd6eff3812d4 192.168.1.100:7005 master - 0 1442466368659 4 connected 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 192.168.1.100:7000 myself,master - 0 0 1 connected 63162ed000db9d5309e622ec319a1dcb29a3304e 192.168.1.100:7001 master - 0 1442466371262 3 connected 45baa2cb45435398ba5d559cdb574cfae4083893 192.168.1.100:7002 master - 0 1442466372264 2 connected cdd5b3a244761023f653e08cb14721f70c399b82 192.168.1.100:7004 master - 0 1442466370261 0 connecte

4.2 角色設置:REPLICATE

結點所有「握手」成功後,就能夠用cluster replicate命令爲結點指定角色了,默認每一個結點都是Master。

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7003 cluster replicate 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c OK [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7004 cluster replicate 63162ed000db9d5309e622ec319a1dcb29a3304e OK [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7005 cluster replicate 45baa2cb45435398ba5d559cdb574cfae4083893 OK [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster nodes 7b953ec26bbdbf67179e5d37e3cf91626774e96f 192.168.1.100:7003 slave 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 0 1442466812984 4 connected 5d9f14cec1f731b6477c1e1055cecd6eff3812d4 192.168.1.100:7005 slave 45baa2cb45435398ba5d559cdb574cfae4083893 0 1442466813986 5 connected 33c0bd93d7c7403ef0239ff01eb79bfa15d2a32c 192.168.1.100:7000 myself,master - 0 0 1 connected 63162ed000db9d5309e622ec319a1dcb29a3304e 192.168.1.100:7001 master - 0 1442466814987 3 connected 45baa2cb45435398ba5d559cdb574cfae4083893 192.168.1.100:7002 master - 0 1442466811982 2 connected cdd5b3a244761023f653e08cb14721f70c399b82 192.168.1.100:7004 slave 63162ed000db9d5309e622ec319a1dcb29a3304e 0 1442466812483 3 connected

 

4.3 槽指派:ADDSLOTS

設置好主從關係以後,就能夠用cluster addslots命令指派16384個槽的位置了。有點噁心的是,ADDSLOTS命令須要在參數中一個個指明槽的ID,而不能指定範圍。這裏用Bash 3.0的特性簡化了,否則就得用Bash的循環來完成了:

[root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7000 cluster addslots {0..5000} OK [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7001 cluster addslots {5001..10000} OK [root@8gVm redis-3.0.4]# src/redis-cli -c -h 192.168.1.100 -p 7001 cluster addslots {10001..16383} OK [root@8gVm redis-3.0.4]# src/redis-trib.rb check 192.168.1.100:7000 Connecting to node 192.168.1.100:7000: OK ... >>> Performing Cluster Check (using node 192.168.1.100:7000) ... [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.

 

這樣咱們就經過手動執行命令獲得了與以前同樣的集羣。

4.4 數據遷移:MIGRATE

真正開始Resharding以前,redis-trib會先在源結點和目的結點上執行cluster setslot <slot> importingcluster setslot <slot> migrating命令,將要遷移的槽分別標記爲遷出中和導入中的狀態。而後,執行cluster getkeysinslot得到Slot中的全部Key。最後就能夠對每一個Key執行migrate命令進行遷移了。槽遷移完成後,執行cluster setslot命令通知整個集羣槽的指派已經發生變化。

關於遷移過程當中的數據訪問,客戶端訪問源結點時,若是Key還在源結點上就直接操做。若是已經不在源結點了,就向客戶端返回一個ASK錯誤,將客戶端重定向到目的結點

4.5 內部數據結構

Redis Cluster功能涉及三個核心的數據結構clusterState、clusterNode、clusterLink都在cluster.h中定義。這三個數據結構中最重要的屬性就是:clusterState.slots、clusterState.slots_to_keys和clusterNode.slots了,它們保存了三種映射關係

  • clusterState:集羣狀態
    • nodes:全部結點
    • migrating_slots_to:遷出中的槽
    • importing_slots_from:導入中的槽
    • slots_to_keys:槽中包含的全部Key,用於遷移Slot時得到其包含的Key
    • slots:Slot所屬的結點,用於處理請求時判斷Key所在Slot是否本身負責
  • clusterNode:結點信息
    • slots:結點負責的全部Slot,用於發送Gossip消息通知其餘結點本身負責的Slot。經過位圖方式保存節省空間,16384/8剛好是2048字節,因此槽總數16384不是隨意定的
  • clusterLink:與其餘結點通訊的鏈接
// 集羣狀態,每一個節點都保存着一個這樣的狀態,記錄了它們眼中的集羣的樣子。 // 另外,雖然這個結構主要用於記錄集羣的屬性,可是爲了節約資源, // 有些與節點有關的屬性,好比 slots_to_keys 、 failover_auth_count // 也被放到了這個結構裏面。 typedef struct clusterState { ... // 指向當前節點的指針 clusterNode *myself; /* This node */ // 集羣當前的狀態:是在線仍是下線 int state; /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */ // 集羣節點名單(包括 myself 節點) // 字典的鍵爲節點的名字,字典的值爲 clusterNode 結構 dict *nodes; /* Hash table of name -> clusterNode structures */ // 記錄要從當前節點遷移到目標節點的槽,以及遷移的目標節點 // migrating_slots_to[i] = NULL 表示槽 i 未被遷移 // migrating_slots_to[i] = clusterNode_A 表示槽 i 要從本節點遷移至節點 A clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS]; // 記錄要從源節點遷移到本節點的槽,以及進行遷移的源節點 // importing_slots_from[i] = NULL 表示槽 i 未進行導入 // importing_slots_from[i] = clusterNode_A 表示正從節點 A 中導入槽 i clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS]; // 負責處理各個槽的節點 // 例如 slots[i] = clusterNode_A 表示槽 i 由節點 A 處理 clusterNode *slots[REDIS_CLUSTER_SLOTS]; // 跳躍表,表中以槽做爲分值,鍵做爲成員,對槽進行有序排序 // 當須要對某些槽進行區間(range)操做時,這個跳躍表能夠提供方便 // 具體操做定義在 db.c 裏面 zskiplist *slots_to_keys; ... } clusterState; // 節點狀態 struct clusterNode { ... // 節點標識 // 使用各類不一樣的標識值記錄節點的角色(好比主節點或者從節點), // 以及節點目前所處的狀態(好比在線或者下線)。 int flags; /* REDIS_NODE_... */ // 由這個節點負責處理的槽 // 一共有 REDIS_CLUSTER_SLOTS / 8 個字節長 // 每一個字節的每一個位記錄了一個槽的保存狀態 // 位的值爲 1 表示槽正由本節點處理,值爲 0 則表示槽並不是本節點處理 // 好比 slots[0] 的第一個位保存了槽 0 的保存狀況 // slots[0] 的第二個位保存了槽 1 的保存狀況,以此類推 unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */ // 指針數組,指向各個從節點 struct clusterNode **slaves; /* pointers to slave nodes */ // 若是這是一個從節點,那麼指向主節點 struct clusterNode *slaveof; /* pointer to the master node */ ... }; /* clusterLink encapsulates everything needed to talk with a remote node. */ // clusterLink 包含了與其餘節點進行通信所需的所有信息 typedef struct clusterLink { ... // TCP 套接字描述符 int fd; /* TCP socket file descriptor */ // 與這個鏈接相關聯的節點,若是沒有的話就爲 NULL struct clusterNode *node; /* Node related to this link if any, or NULL */ ... } clusterLink;

 

4.6 處理流程全梳理

在單機模式下,Redis對請求的處理很簡單。Key存在的話,就執行請求中的操做;Key不存在的話,就告訴客戶端Key不存在。然而在集羣模式下,由於涉及到請求重定向和Slot遷移,因此對請求的處理變得很複雜,流程以下:

  1. 檢查Key所在Slot是否屬於當前Node?
    2.1 計算crc16(key) % 16384獲得Slot
    2.2 查詢clusterState.slots負責Slot的結點指針
    2.3 與myself指針比較
  2. 若不屬於,則響應MOVED錯誤重定向客戶端
  3. 若屬於且Key存在,則直接操做,返回結果給客戶端
  4. 若Key不存在,檢查該Slot是否遷出中?(clusterState.migrating_slots_to)
  5. 若Slot遷出中,返回ASK錯誤重定向客戶端到遷移的目的服務器上
  6. 若Slot未遷出,檢查Slot是否導入中?(clusterState.importing_slots_from)
  7. 若Slot導入中且有ASKING標記,則直接操做
  8. 不然響應MOVED錯誤重定向客戶端

5.應用案例收集

5.1 有道:Redis Cluster使用經驗

詳情請參見原文,關鍵內容摘錄以下:

5.1.1 兩個缺點

「redis cluster的設計在這塊有點奇葩,跟集羣相關的操做須要一個外部的ruby腳原本協助(固然多是爲了讓主程序的代碼足夠簡潔?),而後那個腳本還只支持填實例的ip不支持host,還不告訴你不支持讓你用host以後各類莫名其妙。」

「第一個缺點就是嚴格依賴客戶端driver的成熟度。若是把redis cluster設計成相似Cassandra,請求集羣中任何一個節點均可以負責轉發請求,client會好寫一些。」

「第二個缺點徹底是設計問題了,就是一個redis進程既負責讀寫數據又負責集羣交互,雖然設計者已經儘量簡化了代碼和邏輯,但仍是讓redis從一個內存NoSQL變成了一個分佈式NoSQL。分佈式系統很容易有坑,一旦有坑必須升級redis。」

5.1.2 去中心化 vs. Proxy

「關於redis cluster的設計,Gossip/P2P的去中心化架構自己不是問題,但一旦有了中心節點,能作的事情就多了,好比sharding不均勻是很容易自動rebalance的,而無中心的只能靠外界來搞。而後redis cluster又是slot的形式而非C*式的一致性哈希,新節點分slot又不自動,依賴外界(ruby腳本)來分配顯得不方便更不優美和諧。並且由於是master-slave的系統而非W+R>N的那種,master掛掉以後儘快發現是比較重要的,gossip對於節點掛掉的發現終究沒有中心節點/zookeeper方便快速。」

「基於proxy作轉發意味着屏蔽了下層存儲,徹底能夠根據前綴/tag/冷熱程度,來把部分甚至大多數數據放在磁盤從而節約成本又保證一致性,這都是有中心節點所帶來的好處。」

5.2 奇虎360:Redis Cluster淺析和Bada對比

詳情請參見原文,關鍵內容摘錄以下:

5.2.1 負載均衡問題

「redis cluster的主備是以節點爲單位,而bada則是以partition爲單位,這樣,一樣是3個節點,1024個partition的狀況下,redis cluster的主節點負責整個1024個partition的服務,而兩個從節點則只負責異步備份,致使集羣負載不均,再看bada,將1024個partition的主均分到3個節點中,每一個節點各有主備,主對外提供服務,這樣均分了訪問壓力,有效的利用了資源。」

5.2.2 一致性的保證

redis cluster與bada同樣,最終一致性,讀寫都只請求主節點,當一條寫請求在對應的主節點寫成功後,會馬上返回給客戶端成功,而後主節點經過異步的方式將新的數據同步到對應的從節點,這樣的方式減小了客戶端多個節點寫成功等待的時間,不過在某些狀況下會形成寫丟失:

1)當主節點接受一條寫請求,寫入並返回給客戶端成功後不幸宕掉,此時剛纔的寫還未同步給其對應的從節點,而從節點在發現主節點掛掉並從新選主後,新的主節點則永久丟失了以前老的主節點向用戶確認的寫

2)當網絡發生割裂,將集羣分裂成少數派與多數派,這樣在客戶端不知情的狀況下,會將寫繼續寫入到少數派中的某些主節點中,而當割裂超過必定時長後,集羣感知到異常,此時少數派中的全部主節點會中止響應全部的寫請求,多數派的其對應的從節點則會發起選舉成爲新的主節點,假設過了一會後割裂恢復,老的主節點發現有更新的主存在,自動變成其從節點,而新的主節點中則會永久丟失掉網絡割裂至集羣感知異常進行切主這個階段老主節點確認的全部寫

相對於redis cluster的永久丟失,bada經過binlog merge有效的解決了這一問題。全部partition的主節點在響應客戶端的寫請求時,都會在本地記錄binlog,binlog實質就是帶有時間戳的KV對。當老主以從節點的身份從新加入集羣時,會觸發binlog merge操做,新主會比較而且合併兩者的binlog,這樣就能夠將以前丟失掉得寫再補回來。」

5.2.3 請求重定向問題

「bada服務端節點在收到本不應由本身負責的Partition請求後,不會向客戶端返回重定向信息,而是經過代理的方式,直接在集羣內部向正確節點轉發客戶端的請求,並將結果同meta信息再轉發回客戶端。」

「再看multi key操做,redis cluster爲了追求高性能,支持multi key的前提是全部的key必須在同一個節點中, 不過這樣的處理須要交給用戶,對須要進行multi key操做的全部key,在寫入前人爲的加上hash tags。當redis cluster進行resharding的時候,也就是將某些slot從一個節點遷移到另外一個節點時,此時的multi key操做可能會失敗,由於在遷移的slot中的key此時存在於兩個節點。

bada怎麼作呢?用戶若是對multi key操做性能很在意時,能夠採用與redis cluster一樣的方式,給這些key加上hash tags來讓它們落在同一個節點,若是能夠接受性能的稍微損耗而解放用戶的處理邏輯,則能夠像single key操做同樣,請求任一bada節點,它會代理全部的key請求並將結果返回給用戶。而且在multi key操做在任什麼時候候均可以,即便在進行partition的遷移,bada也會提早進行切主,保證服務的正常提供。」

5.3 芒果TV:Redis服務解決方案

詳情請參見原文,關鍵內容摘錄以下:

芒果TV在Redis Cluster基礎上進行開發,主要增長了兩個組件:

  • 監控管理:以Python爲主要開發框架的Web應用程序Redis-ctl
  • 請求代理:以C++11爲開發語言的輕量數據代理程序cerberus。其做用和優勢爲:
    • 集羣代理程序的自動請求分發/重試機制使得應用沒必要修改自身代碼或更新Redis庫
    • 代理節點爲全部Redis節點加上統一管理和狀態監測, 能夠查閱歷史數據, 或在發生任何問題以後快速響應修復
    • 代理進程的無狀態性使之可在故障後快速恢復, 不影響後端集羣數據完整性

這兩個組件都已開源到GitHub上,你們能夠關注一下!


6.Pros & Cons總結

關於Redis Cluster帶來的種種優點就不說了,在這裏主要是「雞蛋裏挑骨頭」,總結一下現階段集羣功能的欠缺之處和可能的「坑」。

6.1 無中心化架構

6.1.1 Gossip消息

Gossip消息的網絡開銷和時延是決定Redis Cluster可以線性擴展的因素之一。關於這個問題,在《redis cluster百萬QPS的挑戰》一文中有所說起。

6.1.2 結點粒度備份

此外,Redis Cluster也許是爲了簡化設計採用了Master-Slave複製的數據備份方案,並無採起如Cassandra或IMDG等對等分佈式系統中常見的Slot粒度(或叫Partition/Bucket等)的自動冗餘和指派。

這種設計雖然避免比較複雜的分佈式技術,但也帶來了一些問題:

  • Slave徹底閒置:即使是讀請求也不會被重定向到Slave結點上,Slave屬於「冷備」
  • 寫壓力沒法分攤:Slave閒置致使的另外一個問題就是寫壓力也都在Master上

6.2 客戶端的挑戰

因爲Redis Cluster的設計,客戶端要擔負起一部分責任:

  • Cluster協議支持:無論Dummy仍是Smart模式,都要具有解析Cluster協議的能力
  • 網絡開銷:Dummy客戶端不斷重定向的網絡開銷
  • 鏈接維護:Smart客戶端對鏈接到集羣中每一個結點Socket的維護
  • 緩存路由表:Smart客戶端Slot路由表的緩存和更新
  • 內存消耗:Smart客戶端上述維護的信息都是有內存消耗的
  • MultiOp有限支持:對於MultiOp,由客戶端經過KeyTag保證全部Key都在同一Slot。而即使如此,遷移時也會致使MultiOp失敗。同理,對Pipeline和Transaction的支持也受限於必須操做同一Slot內的Key。

6.3 Redis實現問題

儘管屬於無中心化架構一類的分佈式系統,但不一樣產品的細節實現和代碼質量仍是有很多差別的,就好比Redis Cluster有些地方的設計看起來就有一些「奇葩」和簡陋:

  • 不能自動發現:無Auto Discovery功能。集羣創建時以及運行中新增結點時,都要經過手動執行MEET命令或redis-trib.rb腳本添加到集羣中
  • 不能自動Resharding:不只不自動,連Resharding算法都沒有,要本身計算從哪些結點上遷移多少Slot,而後仍是得經過redis-trib.rb操做
  • 嚴重依賴外部redis-trib:如上所述,像集羣健康情況檢查、結點加入、Resharding等等功能全都抽離到一個Ruby腳本中了。還不清楚上面提到的缺失功能將來是要繼續加到這個腳本里仍是會集成到集羣結點中?redis-trib也許要變成Codis中Dashboard的角色
  • 無監控管理UI:即使將來加了UI,像遷移進度這種信息在無中心化設計中很可貴到
  • 只保證最終一致性:寫Master成功後當即返回,如需強一致性,自行經過WAIT命令實現。但對於「腦裂」問題,目前Redis沒提供網絡恢復後的Merge功能,「腦裂」期間的更新可能丟失

6.4 性能損耗

因爲以前手頭沒有空閒的物理機資源,因此只在虛擬機上作了簡單的單機測試,在單獨的一臺壓力機使用YCSB測試框架向虛擬機產生讀寫負載。虛擬機的配置爲8核Intel Xeon CPU X5650@2.67GHz,16GB內存,分別搭建了4結點的單機版Redis和集羣版Redis,測試一下Redis Cluster的性能損耗。因爲不是最近作的測試,因此Jedis用的2.6.2版本。注:固然Redis Cluster能夠經過多機部署得到水平擴展帶來的性能提高,這裏只是因爲環境有限因此作的簡單單機測試。

因爲YCSB自己僅支持Redis單機版,因此須要咱們本身增長擴展插件,具體方法請參照《YCSB性能測試工具使用》。經過YCSB產生2000w隨機數據,Value大約100Byte左右。而後經過YCSB測試Read-Mostly(90% Read)和Read-Write-Mixed(50% Read)兩種狀況:

  • 數據加載:吞吐量上有約18%的降低。
  • Read-Mostly:吞吐量上有約3.5%~7.9%的降低。
  • Read-Write-Mixed:吞吐量上有約3.3%~5.5%降低。
  • 內存佔用:Jedis客戶端多佔用380MB內存。

6.5 最後的總結

從現階段看來,相比Sentinel或Codis等方案,Redis Cluster的優點還真是有限,我的以爲最大的優勢有兩個:

  1. 官方提供的Slot實現而不用像Codis那樣去改源碼了;
  2. 不用額外的Sentinel集羣或相似的代碼實現了。

同其餘分佈式系統,如Cassandra,或內存型的IMDG如Hazelcast和GridGain,除了性能方面外,從功能上Redis Cluster簡直被爆得體無完膚… 看看我以前總結過的GridGain介紹《開源IMDG之GridGain》

  • 結點自動發現和Rebalance
  • 分區粒度的備份
  • 故障時分區角色自動調整
  • 結果聚合(不會重定向客戶端)
  • 「腦裂」恢復後的Merge(Hazelcast支持多種合併策略)
  • 多Primary分區寫操做(見Replicated模式)

這些都是Redis Cluster沒有或者要手動完成的。固然這也不足爲奇,由於這與Redis的設計初衷有關,畢竟做者都已經說了,最核心的設計目標就是性能、水平伸縮和可用性。

從Redis Cluster的環境搭建使用到高級功能和內部原理剖析,再到應用案例收集和優缺點的分析羅列,講了這麼多,關於Redis集羣到底如何,相信你們根據本身切身和項目的具體狀況必定有了本身的結論。無論是評估測試也好,二次開發也好,仍是直接上線使用也好,相信隨着官方的不斷迭代更新和你們的力量,Redis Cluster必定會逐漸完善成熟的!

 

http://www.cnblogs.com/lixigang/articles/4847110.html

相關文章
相關標籤/搜索