Redis叢集解決方案有兩個: 1) Twemproxy: 這是Twitter推出的解決方案,簡單的說就是上層加個代理負責分發,屬于client端叢集方案,目前很多應用者都在采用的解決方案。Twemproxy會用到LVS、Twemproxy、Keepalived、Redis主從模式,有點麻煩,而且沒有線上擴容節點能力,需要一開始就預留出足夠的節點,之前的文章也詳細介紹了Twemproxy這種叢集方式及其部署過程; 2) Redis Cluster: 這是Redis3.0之後,官方推出的server端叢集方案。
Redis 3.0之後支援了Cluster,大大增強了Redis水準擴充的能力。Redis Cluster是Redis官方的叢集實作方案,在此之前已經有第三方Redis叢集解決方案,如Twenproxy、Codis,與其不同的是:Redis Cluster并非使用Porxy模式來連接配接叢集節點,而是使用無中心節點的模式來組建叢集。在Cluster出現之前,隻有Sentinel保證了Redis的高可用性。
Redis Cluster實作在多個節點之間進行資料共享,即使部分節點失效或者無法進行通訊時,Cluster仍然可以繼續處理請求。若每個主節點都有一個從節點支援,在主節點下線或者無法與叢集的大多數節點進行通訊的情況下, 從節點提升為主節點,并提供服務,保證Cluster正常運作,Redis Cluster的節點分片是通過哈希槽(hash slot)實作的,每個鍵都屬于這 16384(0~16383) 個哈希槽的其中一個,每個節點負責處理一部分哈希槽。
前面已經介紹了Redis Cluster叢集及其部署過程,下面再補充下有關Redis Cluster應用原理部分内容,以便更加深刻透徹地了解Redis Cluster。
一、Redis Cluster叢集最核心的三個目标
- 性能:這是Redis賴以生存的看家本領,增加叢集功能後當然不能對性能産生太大影響,是以Redis采取了P2P而非Proxy方式、異步複制、用戶端重定向等設計,而犧牲了部分的一緻性、使用性。
- 水準擴充:叢集的最重要能力當然是擴充,文檔中稱可以線性擴充到1000結點。
- 可用性:在Cluster推出之前,可用性要靠Sentinel保證。有了叢集之後也自動具有了Sentinel的監控和自動Failover能力。
二、Redis架構變化與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理論要求的,三者隻能取其二。
=============Redis Cluster的概念特點============
- 去中心、去中間件,各節點平等,儲存各自資料和叢集狀态,節點間活躍互連。
- 傳統用一緻性哈希配置設定資料,叢集用哈希槽(hash slot)配置設定。 算法為CRC16。
- 預設配置設定16384個slot, 用CRC16算法取模{ CRC16(key)%16384 }計算所屬slot。
- 最少3個主節點
==========簡單來說, Redis Cluster叢集的優點=======
- 官方解決方案
- 可以線上水準擴充(Twemproxy的一大弊端就是不支援線上擴容節點)
- 用戶端直連,系統瓶頸更少
- 無中心架構
- 支援資料分片
三、Redis Cluster叢集部署
1)叢集部署和配置
這個之前已經介紹過了,部署過程參考:http://www.cnblogs.com/kevingrace/p/7846324.html
要想開啟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
配置好後,根據叢集規模,拷貝出來幾份同樣的配置檔案,唯一不同的就是監聽端口,可以依次改為7001、7002… 因為Redis Cluster如果資料備援是1的話,至少要3個Master和3個Slave,是以可以拷貝出6個執行個體的配置檔案。為了避免互相影響,為6個執行個體的配置檔案建立獨立的檔案夾。

2)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
3)叢集建立
首先,啟動配置好的6個Redis執行個體。
[root@8gVm redis-3.0.4]# for ((i=0; i<6; ++i));docd 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 Cluster叢集就已經建立成功了!“貼心”的Redis還在utils/create-cluster下提供了一個create-cluster腳本,能夠建立出一個叢集,類似上面建立起的3主3從的叢集。
4)Redis Cluster叢集簡單測試
連接配接到叢集中的任意一個結點,啟動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
5)Redis Cluster 叢集重新開機
目前redis-trib的功能還比較弱,需要重新開機叢集的話,需要先手動kill掉各個程序,然後重新啟動就可以了。這确實有點太傻X, 網上有人回報說重新開機有問題,不過本人暫時還沒遇到問題。
[root@8gVm redis-3.0.4]# ps -ef | grep redis|grep -v grep | awk '{print $2}' | xargs kill -9
6)Redis Cluster叢集資料遷移
這就需要體驗一下Redis叢集的Resharding功能了~~
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"
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)添加到叢集
使用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.
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"
7)Redis Cluster叢集故障轉移
在高可用性方面,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"
通過上面可以知道,用Redis提供的redis-trib或create-cluster腳本能幾步甚至一步就建立起一個Redis叢集。本篇為了深入了解Redis Cluster的使用者,是以要暫時抛開這些友善的工具,完全手動建立一遍上面的3主3從叢集。
8)Redis Cluster叢集發現: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
9)Redis Cluster叢集的角色設定(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
10)Redis Cluster的槽指派(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.
這樣就通過手動執行指令得到了與之前一樣的叢集。
11)Redis Cluster叢集的資料遷移(MIGRATE)
真正開始Resharding之前,redis-trib會先在源結點和目的結點上執行cluster setslot <slot> importing和cluster setslot <slot> migrating指令,将要遷移的槽分别标記為遷出中和導入中的狀态。然後,執行cluster getkeysinslot獲得Slot中的所有Key。最後就可以對每個Key執行migrate指令進行遷移了。槽遷移完成後,執行cluster setslot指令通知整個叢集槽的指派已經發生變化。
關于遷移過程中的資料通路,用戶端通路源結點時,如果Key還在源結點上就直接操作。如果已經不在源結點了,就向用戶端傳回一個ASK錯誤,将用戶端重定向到目的結點。
12)Redis Cluster叢集内部資料結構
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 也被放到了這個結構裡面。
ypedef 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;
13)Redis Cluster叢集的處理流程全梳理
在單機模式下,Redis對請求的處理很簡單。Key存在的話,就執行請求中的操作;Key不存在的話,就告訴用戶端Key不存在。然而在叢集模式下,因為涉及到請求重定向和Slot遷移,是以對請求的處理變得很複雜,流程如下:
- 檢查Key所在Slot是否屬于目前Node?
- 計算crc16(key) % 16384得到Slot
- 查詢clusterState.slots負責Slot的結點指針
- 與myself指針比較
- 若不屬于,則響應MOVED錯誤重定向用戶端
- 若屬于且Key存在,則直接操作,傳回結果給用戶端
- 若Key不存在,檢查該Slot是否遷出中?(clusterState.migrating_slots_to)
- 若Slot遷出中,傳回ASK錯誤重定向用戶端到遷移的目的伺服器上
- 若Slot未遷出,檢查Slot是否導入中?(clusterState.importing_slots_from)
- 若Slot導入中且有ASKING标記,則直接操作
- 否則響應MOVED錯誤重定向用戶端
14)Redis Cluster叢集現實存在的問題
盡管屬于無中心化架構一類的分布式系統,但不同産品的細節實作和代碼品質還是有不少差異的,就比如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功能,“腦裂”期間的更新可能丢失
============================================redis cluster install ========================================
3主 3從, 從庫交叉存放在 主庫上
主:ip 192.168.1.101 6381 從ip 192.168.1.102 6383
主:IP 192.168.1.102 6382 從ip 192.168.1.103 6381
主:ip 192.168.1.103 6383 從 ip 192.168.1.101 6382
Redis cluster 叢集配置檔案
建立叢集目錄
mkdir /data/redis_data/{conf,data,logs,temp}
101 節點的配置檔案
/data/redis_data/conf/redis-6381.conf
/data/redis_data/conf/redis-6383.conf
102 節點配置檔案
/data/redis_data/conf/redis-6382.conf
/data/redis_data/conf/redis-6381.conf
103 節點配置檔案
/data/redis_data/conf/redis-6383.conf
/data/redis_data/conf/redis-6382.conf
叢集配置檔案内容 範例
================================================================================
################################## NETWORK #####################################
bind 0.0.0.0
protected-mode yes
port 6382
tcp-backlog 511
unixsocket /data/redis_data/temp/redis.sock
unixsocketperm 700
timeout 0
tcp-keepalive 300
################################# GENERAL #####################################
daemonize yes
supervised no
pidfile /data/redis_data/temp/redis_6382.pid
# debug # verbose# notice # warning
loglevel notice
logfile "/data/redis_data/logs/redis_6382.log"
syslog-enabled no
# Specify the syslog identity.
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
databases 1
################################ SNAPSHOTTING ################################
# save <seconds> <changes>
#save 900 1
#save 300 10
#save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression no
rdbchecksum yes
dbfilename dump_6382.rdb
dir /data/redis_data/data
################################# REPLICATION #################################
# slaveof 192.168.1.101 6379
# masterauth <master-password>
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
# repl-ping-slave-period 10
repl-timeout 60
repl-disable-tcp-nodelay no
repl-backlog-size 1mb
repl-backlog-ttl 3600
# By default the priority is 100.
slave-priority 100
# min-slaves-to-write 3
# min-slaves-max-lag 10
# slave-announce-ip 5.5.5.5
# slave-announce-port 1234
################################## SECURITY ###################################
# requirepass foobared
# rename-command CONFIG ""
################################### LIMITS ####################################
maxclients 10000
maxmemory 4294967296
# maxmemory-policy noeviction
# maxmemory-samples 5
############################## APPEND ONLY MODE ###############################
appendonly yes
appendfilename "appendonly_6382.aof"
# appendfsync always
appendfsync everysec
# appendfsync no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
################################ LUA SCRIPTING ###############################
lua-time-limit 5000
################################ REDIS CLUSTER ###############################
cluster-enabled yes
cluster-config-file nodes-6382.conf
cluster-node-timeout 15000
# cluster-migration-barrier 1
# cluster-require-full-coverage yes
################################## SLOW LOG ###################################
slowlog-log-slower-than 10000
slowlog-max-len 128
################################ LATENCY MONITOR ##############################
latency-monitor-threshold 0
############################# EVENT NOTIFICATION ##############################
# PUBLISH __keyspace@0__:foo del
# PUBLISH __keyevent@0__:del foo
# notify-keyspace-events Elg
# notify-keyspace-events Ex
notify-keyspace-events ""
############################### ADVANCED CONFIG ###############################
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
====================================================================================
啟動節點
redis-server /data/redis_data/conf/redis-6381.conf
redis-server /data/redis_data/conf/redis-6383.conf
redis-server /data/redis_data/conf/redis-6382.conf
redis-server /data/redis_data/conf/redis-6381.conf
redis-server /data/redis_data/conf/redis-6383.conf
redis-server /data/redis_data/conf/redis-6382.conf
==============================安裝 redis-trib.rb====================================
安裝ruby 2.4.1.tgz
./configure --prefix=/usr/local/ruby
make
make install
cd /usr/local/ruby
cp ruby /usr/local/bin/
cp gem /usr/local/bin
wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
gem list --check redis gem
cp /data/software/redis-3.2.9/src/redis-trib.rb /usr/local/bin
redis-trib.rb
=============================================叢集模式配置=============================================
以下這種方式貌似不能按照自己的思路添加主從
redis-trib.rb create --replicas 1 192.168.1.101:6381 192.168.1.102:6382 192.168.1.103:6383 192.168.1.102:6381 192.168.1.103:6382 192.168.1.101:6383
思路改為先加主庫 再加從庫
添加主庫
redis-trib.rb create 192.168.1.101:6381 192.168.1.102:6382 192.168.1.103:6383
添加從庫
把 102的6381 作為從庫加入 101的6381
redis-trib.rb add-node --slave 192.168.1.102:6381 192.168.1.101:6381
redis-trib.rb add-node --slave 192.168.1.103:6382 192.168.1.102:6382
redis-trib.rb add-node --slave 192.168.1.101:6383 192.168.1.103:6383
檢測
redis-trib.rb check 192.168.1.101:6381
redis-trib.rb check 192.168.1.102:6382
redis-trib.rb check 192.168.1.103:6383
随便連結一個就行了,
四、Redis Cluster容錯機制failover總結
failover是redis cluster的容錯機制,是redis cluster最核心功能之一;它允許在某些節點失效情況下,叢集還能正常提供服務。
redis cluster采用主從架構,任何時候隻有主節點提供服務,從節點進行熱備份,故其容錯機制是主從切換機制,即主節點失效後,選取一個從節點作為新的主節點。在實作上也複用了舊版本的主從同步機制。
從縱向看,redis cluster是一層架構,節點分為主節點和從節點。從節點挂掉或失效,不需要進行failover,redis cluster能正常提供服務;主節點挂掉或失效需要進行failover。另外,redis cluster還支援manual failover,即人工進行failover,将從節點變為主節點,即使主節點還活着。下面将介紹這兩種類型的failover。
1)主節點失效産生的failover
a)(主)節點失效檢測
一般地,叢集中的節點會向其他節點發送PING資料包,同時也總是應答(accept)來自叢集連接配接端口的連接配接請求,并對接收到的PING資料包進行回複。當一個節點向另一個節點發PING指令,但是目标節點未能在給定的時限(node timeout)内回複時,那麼發送指令的節點會将目标節點标記為PFAIL(possible failure)。
由于節點間的互動總是伴随着資訊傳播的功能,此時每次當節點對其他節點發送 PING 指令的時候,就會告知目标節點此時叢集中已經被标記為PFAIL或者FAIL标記的節點。相應的,當節點接收到其他節點發來的資訊時, 它會記下那些被其他節點标記為失效的節點。 這稱為失效報告(failure report)。
如果節點已經将某個節點标記為PFAIL,并且根據節點所收到的失效報告顯式,叢集中的大部分其他主節點(n/2+1)也認為那個節點進入了失效狀态,那麼節點會将那個PFAIL節點的狀态标記為FAIL。
一旦某個節點被标記為FAIL,關于這個節點已失效的資訊就會被廣播到整個叢集,所有接收到這條資訊的節點都會将失效節點标記為FAIL。
b)選舉主節點
一旦某個主節點進入 FAIL 狀态, 叢集變為FAIL狀态,同時會觸發failover。failover的目的是從從節點中選舉出新的主節點,使得叢集恢複正常繼續提供服務。
整個主節點選舉的過程可分為申請、授權、更新、同步四個階段:
(1)申請
新的主節點由原已失效的主節點屬下的所有從節點中自行選舉産生,從節點的選舉遵循以下條件:
a、這個節點是已下線主節點的從節點;
b、已下線主節點負責處理的哈希槽數量非空;
c、主從節點之間的複制連接配接的斷線時長有限,不超過 ( (node-timeout * slave-validity-factor) + repl-ping-slave-period )。
如果一個從節點滿足了以上的所有條件,那麼這個從節點将向叢集中的其他主節點發送授權請求,詢問它們是否允許自己更新為新的主節點。
從節點發送授權請求的時機會根據各從節點與主節點的資料偏差來進行排序,讓偏差小的從節點優先發起授權請求。
(2)授權
其他主節點會遵信以下三點标準來進行判斷:
a、 發送授權請求的是從節點,而且它所屬的主節點處于FAIL狀态 ;
b、 從節點的currentEpoch〉自身的currentEpoch,從節點的configEpoch>=自身儲存的該從節點的configEpoch;
c、 這個從節點處于正常的運作狀态,沒有被标記為FAIL或PFAIL狀态;
如果發送授權請求的從節點滿足以上标準,那麼主節點将同意從節點的更新要求,向從節點傳回CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK授權。
(3)更新
一旦某個從節點在給定的時限内得到大部分主節點(n/2+1)的授權,它就會接管所有由已下線主節點負責處理的哈希槽,并主動向其他節點發送一個PONG資料包,包含以下内容:
a、 告知其他節點自己現在是主節點了
b、 告知其他節點自己是一個ROMOTED SLAVE,即已更新的從節點;
c、告知其他節點都根據自己新的節點屬性資訊對配置進行相應的更新
(4)同步
其他節點在接收到ROMOTED SLAVE的告知後,會根據新的主節點對配置進行相應的更新。特别地,其他從節點會将新的主節點設為自己的主節點,進而與新的主節點進行資料同步。
至此,failover結束,叢集恢複正常狀态。
此時,如果原主節點恢複正常,但由于其的configEpoch小于其他節點儲存的configEpoch(failover了産生較大的configEpoch),故其配置會被更新為最新配置,并将自己設新主節點的從節點。
另外,在failover過程中,如果原主節點恢複正常,failover中止,不會産生新的主節點。
2)Manual Failover
Manual Failover是一種運維功能,允許手動設定從節點為新的主節點,即使主節點還活着。
Manual Failover與上面介紹的Failover流程大都相同,除了下面兩點不同:
a)觸發機制不同,Manual Failover是通過用戶端發送cluster failover觸發,而且發送對象隻能是從節點;
b)申請條件不同,Manual Failover不需要主節點失效,failover有效時長固定為5秒,而且隻有收到指令的從節點才會發起申請。
另外,Manual Failover分force和非force,差別在于:非force需要等從節點完全同步完主節點的資料後才進行failover,保證不丢失資料,在這過程中,原主節點停止寫操作;而force不進行進行資料完整同步,直接進行failover。
3)叢集狀态檢測
叢集有OK和FAIL兩種狀态,可以通過CLUSTER INFO指令檢視。當叢集發生配置變化時, 叢集中的每個節點都會對它所知道的節點進行掃描,隻要叢集中至少有一個哈希槽不可用(即負責該哈希槽的主節點失效),叢集就會進入FAIL狀态,停止處理任何指令。
另外,當大部分主節點都進入PFAIL狀态時,叢集也會進入FAIL狀态。這是因為要将一個節點從PFAIL狀态改變為FAIL狀态,必須要有大部分主節點(n/2+1)認可,當叢集中的大部分主節點都進入PFAIL時,單憑少數節點是沒有辦法将一個節點标記為FAIL狀态的。 然而叢集中的大部分主節點(n/2+1)進入了下線狀态,讓叢集變為FAIL,是為了防止少數存着主節點繼續處理使用者請求,這解決了出現網絡分區時,一個可能被兩個主節點負責的哈希槽,同時被使用者進行讀寫操作(通過禁掉其中少數派讀寫操作,證保隻有一個讀寫操作),造成資料丢失資料問題。
說明:上面n/2+1的n是指叢集裡有負責哈希槽的主節點個數。
*************** 當你發現自己的才華撐不起野心時,就請安靜下來學習吧!***************