1. 程式人生 > >Redis Cluster集群總結性梳理

Redis Cluster集群總結性梳理

from 等等 skiplist 數據訪問 更新 1.10 都沒有 daemon rst

前面已經介紹了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集群部署

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集群總結性梳理