Redis集羣解決方案有兩個: 1) Twemproxy: 這是Twitter推出的解決方案,簡單的說就是上層加個代理負責分發,屬於client端集羣方案,目前不少應用者都在採用的解決方案。Twemproxy會用到LVS、Twemproxy、Keepalived、Redis主從模式,有點麻煩,並且沒有在線擴容節點能力,須要一開始就預留出足夠的節點,以前的文章也詳細介紹了Twemproxy這種集羣方式及其部署過程; 2) Redis Cluster: 這是Redis3.0以後,官方推出的server端集羣方案。html
Redis 3.0以後支持了Cluster,大大加強了Redis水平擴展的能力。Redis Cluster是Redis官方的集羣實現方案,在此以前已經有第三方Redis集羣解決方案,如Twenproxy、Codis,與其不一樣的是:Redis Cluster並不是使用Porxy模式來鏈接集羣節點,而是使用無中心節點的模式來組建集羣。在Cluster出現以前,只有Sentinel保證了Redis的高可用性。node
Redis Cluster實如今多個節點之間進行數據共享,即便部分節點失效或者沒法進行通信時,Cluster仍然能夠繼續處理請求。若每一個主節點都有一個從節點支持,在主節點下線或者沒法與集羣的大多數節點進行通信的狀況下, 從節點提高爲主節點,並提供服務,保證Cluster正常運行,Redis Cluster的節點分片是經過哈希槽(hash slot)實現的,每一個鍵都屬於這 16384(0~16383) 個哈希槽的其中一個,每一個節點負責處理一部分哈希槽。git
前面已經介紹了Redis Cluster集羣及其部署過程,下面再補充下有關Redis Cluster應用原理部份內容,以便更加深入透徹地理解Redis Cluster。github
1、Redis Cluster集羣最核心的三個目標redis
2、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的一大弊端就是不支持在線擴容節點)
- 客戶端直連,系統瓶頸更少
- 無中心架構
- 支持數據分片ruby
3、Redis Cluster集羣部署bash
1)集羣部署和配置
這個以前已經介紹過了,部署過程參考:http://www.cnblogs.com/kevingrace/p/7846324.html
要想開啓Redis Cluster模式,有幾項配置是必須的,還能夠額外添加一些配置:
配置好後,根據集羣規模,拷貝出來幾份一樣的配置文件,惟一不一樣的就是監聽端口,能夠依次改成700一、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了,它們保存了三種映射關係:
集羣狀態,每一個節點都保存着一個這樣的狀態,記錄了它們眼中的集羣的樣子。另外,雖然這個結構主要用於記錄集羣的屬性,可是爲了節約資源,有些與節點有關的屬性,好比 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遷移,因此對請求的處理變得很複雜,流程以下:
14)Redis Cluster集羣現實存在的問題
儘管屬於無中心化架構一類的分佈式系統,但不一樣產品的細節實現和代碼質量仍是有很多差別的,就好比Redis Cluster有些地方的設計看起來就有一些「奇葩」和簡陋:
============================================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 隨便連接一個就好了,
4、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是指集羣裏有負責哈希槽的主節點個數。