1、集群原理簡介1.1、什么是集群?什么是分區?
集群簡單的說就是將同一個服務部署在不同的機器上,從而提高服務的橫向擴展能力。
分區就是將數據分布在多個實例(服務器)上,讓每一個實例都只存儲一部分數據,從而達到增大總的存儲數據量的效果。
1.2、為什么要實用集群?
為什么要實用集群呢?主從+哨兵模式不是已經很好了嗎,已經高可用了嗎?
但是主從+哨兵雖然解決了高可用問題,但是沒有解決數據分區存儲問題。因為我們存儲的數據量大小取決于主服務器的存儲容量。那么集群模式將數據分區存儲就是為了實現數據存儲量可以橫向擴展。
1.3、數據分區的優點與缺點?
優點
- 1、性能提升
-
- 單機redis的網絡I/O能力和計算能力是有限的,將請求分發到多臺機器,充分利用多臺機器的計算能力和帶寬,有助于redis總體的服務能力。
- 2、存儲能力橫向擴展
-
- 即使Redis的存儲能力已經能滿足大部分需求,但是隨著key的數量不斷增加,單臺機器受限于機器本身的存儲容量,將數據分散到多臺機器存儲使得Redis服務可以橫向擴展。
缺點
- 1、管理更加復雜
-
- Redis數據分散到多臺機器,管理起來自然會更加的復雜。還涉及到數據遷移等工作,增大了操作難度。
- 2、分散后的數據節點宕機使得集群缺少部分數據(可以使用主從解決)
一般按照分區鍵(ID)進行分區,分區方式一般有范圍分區和Hash分區方式。
1.4.1、范圍分區
根據ID數字額范圍分區,比如:1-10000,100001-20000...90001-100000。每個范圍分到不同得Redis實例中。
優點
實現簡單,方便遷移和擴展
缺點
數據分布不均勻,可以少數節點占據了大多數的數據。性能損失比較嚴重。
非數字型的key,比如UUID就無法使用,當然,可以使用雪花算法代替ID的生成。雪花算法是數值且能夠排序。
1.4.2、Hash分區
利用簡單的hash算法就可以,比如根據key求hash值,對Redis實例的總數執行取模操作,從而計算落在哪個Redis實例上。
優點
支持所有key類型, key分布均勻, 性能比較好
缺點
遷移復雜,需要重新計算,擴展較差(但是可以利用一致性hash環拓展)。 而Hash算法在客戶端鏈接服務端時,被廣泛使用(比如:JedisPool)。
1.5、客戶端分區
對于給定一個key, 客戶端可以直接選擇正確的節點進行讀寫。許多客戶端都實現了客戶端分區(比如:RedisPool),當然,也可以自己編碼是現實。
客戶端有很多種計算key的落地到那個Redis實例的算法,這個時最普通的hash算法。
1.5.1、普通hash算法
計算公式:hash(key)%N;hash:可以采用hash算法,比如CRC32、CRC16等;N:是Redis主機個數。
優點
數據分布均勻,實現簡單
缺點
節點數量固定,擴展的話需要重新計算hash。
查詢必須采用分片的key來查詢,一旦key變化了,數據就查詢不出來了,所以不要輕易改變key的分區。
1.5.1、一致性hash算法1.5.1.1、算法簡介
普通hash是對主機數量取模,而一致性hash是對2^32(4 294 967 296)取模(一個足夠大的hash表)。
我們把2^32想象成一個圓,就像鐘表一樣,鐘表的圓可以理解成由60個點組成的圓,而此處我們把這個圓想象成由2^32個點組成的圓,示意圖如下:
圓環的正上方的點代表0,0點右側的第一個點代表1,以此類推,2、3、4、5、6……直到2^32-1,也就是說0點左側的第一個點代表2^32-1 。我們把這個由2的32次方個點組成的圓環稱為hash環。
假設我們有3臺緩存服務器(A/B/C),使用它們各自的IP地址進行哈希計算,使用哈希后的結果對2^32取模,可以使用如下公式:
hash("服務器的IP地址") % 2^32
通過上述公式算出的結果一定是一個0到2^32-1之間的一個整數,我們就用算出的這個整數,分別代表服務器(A/B/C).
既然這個整數肯定處于0到2^32-1之間,那么,上圖中的hash環上必定有一個點與這個整數對應,也就是服務器A/B/C就可以映射到這個環上,如下圖:
假設,我們需要使用Redis緩存數據,那么我們使用如下公式可以將數據映射到上圖中的hash環上
hash(key) % 2^32
現在服務器與數據都被映射到了hash環上,上圖中的數據將會被緩存到服務器A上。
因為從數據的開始位置,沿順時針方向遇到的第一個服務器就是A服務器,所以,上圖中的數據將會被緩存到服務器A上。
1.5.1.2、一致性hash算法有缺點
優點
添加或移除節點時,數據只需要做部分的遷移,比如上圖中把C服務器移除,則數據4遷移到服務器A中,而其他的數據保持不變。
缺點
數據分布不均勻,可能出現所有的key都被hash到同一個節點上了,折中現象叫做hash環偏移。
理論上我們可以增加服務器數量來減少便宜,但是這樣成本太高了。所以通過增加虛擬節點來處理。
1.5.1.3、一致性hash虛擬節點
"虛擬節點"是"實際節點"(實際的物理服務器)在hash環上的復制品,一個實際節點可以對應多個虛擬節點。
從上圖可以看出,A、B、C三臺服務器分別虛擬出了一個虛擬節點,當然,如果你需要,也可以虛擬出更多的虛擬節點。
引入虛擬節點的概念后,緩存的分布就均衡多了,上圖中,1和3號數據被緩存在服務器A中,4和5號數據被緩存在服務器B中,2和6號數據被緩存在服務器C中.
如果你還不放心,可以虛擬出更多的虛擬節點,以便減小hash環偏斜所帶來的影響,虛擬節點越多,hash環上的節點就越多,緩存被均勻分布的概率就越大。
一般來說,一致性hash算法+虛擬節點就是一個很好的方案了。
1.5.1.4、一致性hash算法實現
一致性hash算法(無虛擬節點)
public class HashDemo { public static void main(String[] args) { //step1 初始化:把服務器節點IP的哈希值對應到哈希環上 // 定義服務器ip String[] servers = new String[]{"192.168.222.101", "192.168.222.102", "192.168.222.103"}; // 創建一個排序的hashMap,key存儲hash值,value存儲服務器IP地址,并按照Hash值排序 SortedMap hashServerMap = new TreeMap<>(); for (String redisServer : servers) { // 求出每?個ip的hash值,對應到hash環上,存儲hash值與ip的對應關系 int serverHash = Math.abs(redisServer.hashCode()); // 存儲hash值與ip的對應關系 hashServerMap.put(serverHash, redisServer); } //step2 針對客戶端IP求出hash值 // 定義客戶端傳遞過來的RedisKey String[] redisKeys = new String[]{"user:001:name", "user:001:age", "user:001:sex"}; for (String redisKey : redisKeys) { // 計算redisKey的hash值 int redisKeyHash = Math.abs(redisKey.hashCode()); //step3 針對客戶端,找到能夠處理當前RedisKey的服務器(哈希環上順時針最近) // 根據redisKey的哈希值去找出哪?個服務器節點能夠處理 // tailMap返回此映射的鍵大于或等于fromKey的部分,也就是比redisKey的hash值大的排序列表,取第一個就是最近的服務器節點 SortedMap filteredSortedMap = hashServerMap.tailMap(redisKeyHash); // 獲取key落到那臺服務器,filteredSortrdMap為空,直接取服務器列表hashServerMap第一個,不為空,則取出最近一個filteredSortrdMap Integer hashKey = filteredSortedMap.isEmpty() ? hashServerMap.firstKey() : filteredSortedMap.firstKey(); System.out.println("==========>>>>RedisKey:" + redisKey + " 被路由到服務器:" + hashServerMap.get(hashKey)); } } }
運行結果
==========>>>>RedisKey:user:001:name 被路由到服務器:192.168.222.101 ==========>>>>RedisKey:user:001:age 被路由到服務器:192.168.222.101 ==========>>>>RedisKey:user:001:sex 被路由到服務器:192.168.222.101
可以看出,Redis的key被路由到同一個節點了,我們使用增加虛擬節點來避免折中情況。
一致性hash算法(有虛擬節點)
public class HashDemo2 { public static void main(String[] args) { //step1 初始化:把服務器節點IP的哈希值對應到哈希環上 // 定義服務器ip String[] servers = new String[]{"192.167.222.101", "192.168.222.103", "191.169.222.123"}; // 創建一個排序的hashMap,key存儲hash值,value存儲服務器IP地址,并按照Hash值排序 SortedMap hashServerMap = new TreeMap<>(); // 定義針對每個真實服務器虛擬出來?個節點 int virtualCount = 3; for (String redisServer : servers) { // 求出每?個ip的hash值,對應到hash環上,存儲hash值與ip的對應關系 int serverHash = Math.abs(redisServer.hashCode()); // 存儲hash值與ip的對應關系 hashServerMap.put(serverHash, redisServer); // 處理虛擬節點 for(int i = 0; i < virtualCount; i++) { int virtualHash = Math.abs((redisServer + "#" + i).hashCode()); hashServerMap.put(virtualHash,"----由虛擬節點"+ i + "映射過來的請求:"+ redisServer); } } //step2 針對客戶端IP求出hash值 // 定義客戶端傳遞過來的RedisKey String[] redisKeys = new String[]{ "user:001:name", "order:001:name", "product:001:name", "user:002:name", "order:002:name", "product:002:name", }; for (String redisKey : redisKeys) { // 計算redisKey的hash值 int redisKeyHash = Math.abs(redisKey.hashCode()); //step3 針對客戶端,找到能夠處理當前RedisKey的服務器(哈希環上順時針最近) // 根據redisKey的哈希值去找出哪?個服務器節點能夠處理 // tailMap返回此映射的鍵大于或等于fromKey的部分,也就是比redisKey的hash值大的排序列表,取第一個就是最近的服務器節點 SortedMap filteredSortedMap = hashServerMap.tailMap(redisKeyHash); // 獲取key落到那臺服務器,filteredSortrdMap為空,直接取服務器列表hashServerMap第一個,不為空,則取出最近一個filteredSortrdMap Integer hashKey = filteredSortedMap.isEmpty() ? hashServerMap.firstKey() : filteredSortedMap.firstKey(); System.out.println("==========>>>>RedisKey:" + redisKey + " 被路由到服務器:" + hashServerMap.get(hashKey)); } } }
運行結果
==========>>>>RedisKey:user:001:name 被路由到服務器:192.167.222.101 ==========>>>>RedisKey:order:001:name 被路由到服務器:191.169.222.123 ==========>>>>RedisKey:product:001:name 被路由到服務器:191.169.222.123 ==========>>>>RedisKey:user:002:name 被路由到服務器:----由虛擬節點2映射過來的請求:191.169.222.123 ==========>>>>RedisKey:order:002:name 被路由到服務器:191.169.222.123 ==========>>>>RedisKey:product:002:name 被路由到服務器:191.169.222.123
可以看出,虛擬節點確實生效了。
2、Redis集群
Redis3.0之后,Redis官方提供了完整的集群解決方案,稱為Rediscluster。
Redis集群方案采用去中心化的方式,包括:sharding(分區)、replication(復制)、failover(故障轉移)。
Redis5.0前采用redis-trib進行集群的創建和管理,需要ruby支持。Redis5.0可以直接使用Redis-cli進行集群的創建和管理。
2.1、Redis集群部署架構
Redis的部署架構時沒有中心的,每個節點都是主節點,是一個P2P(點對點)的去中心化集群架構,依靠gossip協議用于集群間傳播。
2.2、Gossip協議
Gossip協議是一個通信協議,一種傳播消息的方式,它起源于病毒傳播。
Gossip協議基本思想
一個節點周期性(每秒)隨機選擇一些節點,并把信息傳遞給這些節點。
這些收到信息的節點接下來會做同樣的事情,即把這些信息傳遞給其他一些隨機選擇的節點。
信息會周期性的傳遞給N個目標節點。這個N被稱為fanout(扇出)。
gossip協議包含多種消息,包括meet、ping、pong、fail、publish等等。
- 1、meet:sender向receiver發出,請求receiver加入sender的集群(加入集群時的協議)
- 2、ping:節點檢測其他節點是否在線
- 3、pong:receiver收到meet或ping后的回復信息;在failover后,新的Master也會廣播pong
- 4、fail:節點A判斷節點B下線后,A節點廣播B的fail信息,其他收到節點會將B節點標記為下線
- 5、節點A收到publish命令,執行該命令,并向集群廣播publish命令,收到publish命令的節點都會執行相同的publish命令
通過gossip協議,cluster可以提供集群間狀態同步更新、自動選舉,故障轉移(failover)等重要的集群功能.
2.3、Redis的hash槽
redis-cluster把所有的物理節點映射到[0-16383]個slot上,基本上采用平均分配和連續分配的方式。
比如部署架構圖中有5個主節點,這樣在RedisCluster創建時,slot槽可按如下分配:
Redis1 0-3270, Redis2 3271-6542, Redis3 6543-9814, Redis4 9815-13087, Redis5 13088-16383
cluster負責維護節點和slot槽的對應關系 value------>slot-------->節點
當需要在Redis集群中放置一個key-value時,redis先對key使用crc16算法算出一個結果,然后把結果對16384求余數,這樣每個key都會對應一個編號在0-16383之間的哈希槽,redis會根據節點數量大致均等的將哈希槽映射到不同的節點。
2.5、RedisCluster的優點
- 1、高性能:Redis的性能和單機版時同級別的,但是存在多個主節點,可以做負載均衡,讀寫分離等。
- 2、高可用:Redis分片集群的同時,對每個節點都可以采用主從復制保障每個節點的高可用。
- 3、故障轉移,Redis也實現了一個類似Raft的方式,保證整個集群的可用性。failover故障自動轉移。
- 4、易擴展:向Redis增加,移除節點是透明的,無需停機。水平和垂直方向都非常容易擴展。
- 5、Redis官方實現:部署RedisCluster不需要其他代理或者工具,使用Redis-Cli即可。是Redis官方開發的,與單機版幾乎完全兼容。
生產環境中的Redis服務器最少三臺主服務器,三臺從服務器。這里由于條件有限,在同一臺機器上處理,也就是實現偽分布式集群。
下載Redis
下載地址:https://download.redis.io/releases/redis-5.0.0.tar.gz
安在Redis單機版
請查看<> 中,地址為:https://www.toutiao.com/article/7140648630402204168
準備工作
# 創建redis集群文件夾 mkdir -p /opt/redis/redis_cluster # 復制一份編譯好的Redis命令 cp -r /opt/redis/baseredis /opt/redis/redis_cluster/redis1 # 復制配置文件 cp /opt/redis/redis-5.0.0/redis.conf /opt/redis/redis_cluster/redis1/redis.conf # 復制六份,分別修改配置文件 cp -r redis1 redis2 ; cp -r redis1 redis3 ; cp -r redis1 redis4 ; cp -r redis1 redis5 ; cp -r redis1 redis6
redis.conf配置文件修改
##################################.NETWORK ##################################### # 注釋掉bind 127.0.0.1,不然ip地址只能使用127.0.0.1訪問 # bind 127.0.0.1 # 端口號(需要修改) port 7001 tcp-backlog 511 timeout 0 tcp-keepalive 300 ################################# GENERAL ##################################### # 開啟后臺啟動(修改為yes) daemonize yes supervised no # 進程ID(修改為對應端口號的pid) pidfile /var/run/redis_7001.pid loglevel notice logfile "" databases 16 always-show-logo yes ################################ SNAPSHOTTING ################################ save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbchecksum yes dbfilename dump.rdb dir ./ ################################# REPLICATION ################################# # 主從復制 # replicaof # 連接主服務器認證密碼(修改為集群的密碼,每臺機器都一樣) masterauth abcAbc123. replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-disable-tcp-nodelay no # By default the priority is 100. replica-priority 100 ################################## SECURITY ################################### # 認證密碼(修改為集群的密碼,每臺機器都一樣) requirepass abcAbc123. ############################# LAZY FREEING #################################### lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no ############################## AppEND ONLY MODE ############################### # 是否開啟AOF(修改為yes, 開啟aof) appendonly yes # 開啟aof后的文件名 appendfilename "appendonly.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 aof-use-rdb-preamble yes ################################ LUA SCRIPTING ############################### # Set it to 0 or a negative value for unlimited execution without warnings. lua-time-limit 5000 ################################ REDIS CLUSTER ############################### # 是否開啟集群(修改為yes,開啟集群功能) cluster-enabled yes # 集群節點的配置文件(修改為對應端口號的集群端口號對用節點) cluster-config-file nodes-7001.conf # 節點間通信超時時間 cluster-node-timeout 15000 ################################## SLOW LOG ################################### slowlog-max-len 128 ################################ LATENCY MONITOR ############################## latency-monitor-threshold 0 ############################# EVENT NOTIFICATION ############################## 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 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes
同樣的配置復制6份,修改為端口號相關的東西,比如端口號,進程ID文件,集群節點名稱等。可以使用以上配置批量替換
vi編輯器中執行
# 將7001替換成7002 /g表示全部替換 :%s/7001/7002/g
啟動Redis
cd /opt/redis/redis_cluster/redis1/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis2/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis3/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis4/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis5/ && ./bin/redis-server redis.conf cd /opt/redis/redis_cluster/redis6/ && ./bin/redis-server redis.conf
查看redis啟動情況
[root@VM-0-5-centos redis6]# ps -ef |grep redis root 1485 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7001 [cluster] root 1604 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7002 [cluster] root 1606 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7003 [cluster] root 1611 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7004 [cluster] root 1617 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7005 [cluster] root 1630 1 0 22:22 ? 00:00:00 ./bin/redis-server *:7006 [cluster]
開啟防火墻
firewall-cmd --zone=public --add-port=7001/tcp --permanent firewall-cmd --zone=public --add-port=7002/tcp --permanent firewall-cmd --zone=public --add-port=7003/tcp --permanent firewall-cmd --zone=public --add-port=7004/tcp --permanent firewall-cmd --zone=public --add-port=7005/tcp --permanent firewall-cmd --zone=public --add-port=7006/tcp --permanent firewall-cmd --zone=public --add-port=17001/tcp --permanent firewall-cmd --zone=public --add-port=17002/tcp --permanent firewall-cmd --zone=public --add-port=17003/tcp --permanent firewall-cmd --zone=public --add-port=17004/tcp --permanent firewall-cmd --zone=public --add-port=17005/tcp --permanent firewall-cmd --zone=public --add-port=17006/tcp --permanent systemctl restart firewalld.service
放行云服務器的防火墻端口(7001-7006,17001-17006)于本機公網IP
開啟集群(公網IP,指定密碼)
/opt/redis/redis_cluster/redis1/bin/redis-cli --cluster create 162.14.74.11:7001 162.14.74.11:7002 162.14.74.11:7003 162.14.74.11:7004 162.14.74.11:7005 162.14.74.11:7006 --cluster-replicas 1 -a abcAbc123.
- 1、 --cluster-replicas表示副本數量為1,六臺Redis實例,復本為1,也就是三主三從。
- 2、 -a表示指定密碼
加入集群結果
[root@VM-0-5-centos redis6]# /opt/redis/redis_cluster/redis1/bin/redis-cli --cluster create > 162.14.74.11:7001 > 162.14.74.11:7002 > 162.14.74.11:7003 > 162.14.74.11:7004 > 162.14.74.11:7005 > 162.14.74.11:7006 > --cluster-replicas 1 -a abcAbc123. Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 162.14.74.11:7004 to 162.14.74.11:7001 Adding replica 162.14.74.11:7005 to 162.14.74.11:7002 Adding replica 162.14.74.11:7006 to 162.14.74.11:7003 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 95bb2b273537ec44879178d80fec968a4a02d151 162.14.74.11:7001 slots:[0-5460] (5461 slots) master M: 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 162.14.74.11:7002 slots:[5461-10922] (5462 slots) master M: 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 162.14.74.11:7003 slots:[10923-16383] (5461 slots) master S: b7cefab8fbce66cf7784b52a18c1d07fbc485e8d 162.14.74.11:7004 replicates 95bb2b273537ec44879178d80fec968a4a02d151 S: 9d6cc857350689586336dfd6d82f0fbb41dd8450 162.14.74.11:7005 replicates 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 S: 830c13e63211ef4c4dc2668581895f78b45ff06c 162.14.74.11:7006 replicates 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 Can I set the above configuration? (type 'yes' to accept): yes # 這里輸入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 162.14.74.11:7001) M: 95bb2b273537ec44879178d80fec968a4a02d151 162.14.74.11:7001 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 830c13e63211ef4c4dc2668581895f78b45ff06c 162.14.74.11:7006 slots: (0 slots) slave replicates 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 M: 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 162.14.74.11:7003 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 162.14.74.11:7002 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: b7cefab8fbce66cf7784b52a18c1d07fbc485e8d 162.14.74.11:7004 slots: (0 slots) slave replicates 95bb2b273537ec44879178d80fec968a4a02d151 S: 9d6cc857350689586336dfd6d82f0fbb41dd8450 162.14.74.11:7005 slots: (0 slots) slave replicates 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 [OK] All nodes agree about slots configuration. # 加入集群成功 >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
連接Redis進行測試
# 使用集群方式連接Redis /opt/redis/redis_cluster/redis1/bin/redis-cli -h 162.14.74.11 -p 7001 -c # 使用密碼認證 162.14.74.11:7001> auth abcAbc123. OK # 使用cluster nodes查詢集群節點 162.14.74.11:7001> cluster nodes 830c13e63211ef4c4dc2668581895f78b45ff06c 162.14.74.11:7006@17006 slave 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 0 1662994023610 6 connected 95bb2b273537ec44879178d80fec968a4a02d151 10.0.0.5:7001@17001 myself,master - 0 1662994021000 1 connected 0-5460 478f97fc6bc954daadc1aeed49bd90b5b8f921a9 162.14.74.11:7003@17003 master - 0 1662994022610 3 connected 10923-16383 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 162.14.74.11:7002@17002 master - 0 1662994024612 2 connected 5461-10922 b7cefab8fbce66cf7784b52a18c1d07fbc485e8d 162.14.74.11:7004@17004 slave 95bb2b273537ec44879178d80fec968a4a02d151 0 1662994022000 4 connected 9d6cc857350689586336dfd6d82f0fbb41dd8450 162.14.74.11:7005@17005 slave 840c4b75f4603e1e1baa3189154adc9c2dc9abc7 0 1662994023000 5 connected
Redis集群搭建(Docker方式)
使用Docker集群也是一樣的道理。我們使用host模式(直接使用本機IP),實現集群。
準備工作
# 創建docker方式redis集群文件夾 mkdir -p /opt/redis/docker_redis_cluster # 拷貝準備好的文件到該目錄 # 如果沒有安裝tree 先安裝 yum install tree -y
使用tree查看目錄結構, 只需要創建配置文件目錄進行掛載修改,data和logs會自動掛載。
. ├── docker-compose.yml ├── redis1 │ └── conf │ └── redis.conf ├── redis2 │ └── conf │ └── redis.conf ├── redis3 │ └── conf │ └── redis.conf ├── redis4 │ └── conf │ └── redis.conf ├── redis5 │ └── conf │ └── redis.conf └── redis6 └── conf └── redis.conf
再使用ls -al查詢隱藏文件
[root@VM-0-5-centos docker_redis_cluster]# ls -al total 40 drwxr-xr-x 8 root root 4096 Sep 14 21:29 . drwxr-xr-x 8 root root 4096 Sep 12 22:49 .. -rw-r--r-- 1 root root 2953 Sep 14 21:29 docker-compose.yml -rw-r--r-- 1 root root 24 Sep 13 21:23 .env drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis1 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis2 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis3 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis4 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis5 drwxr-xr-x 3 root root 4096 Sep 14 21:31 redis6
可以看到我們準備了docker-compose.yml, .env, redis.conf三種文件。只需要修改.env的變量,就可以通過docker-compose實現一鍵集群了。
.env
# 服務器的IP地址 公網IP 注意開啟防火墻端口 和 公網的安全組 SERVICE_IP=162.14.74.11 # 根目錄 BASE_DIR=/opt/redis/docker_redis_cluster
編寫docker-compose.yml
version: '2' services: redis_cluster: image: redis hostname: redis_cluster container_name: redis_cluster command: 'redis-cli --cluster create ${SERVER_IP}:7001 ${SERVER_IP}:7002 ${SERVER_IP}:7003 ${SERVER_IP}:7004 ${SERVER_IP}:7005 ${SERVER_IP}:7006 --cluster-yes --cluster-replicas 1 -a abcAbc123.' depends_on: - redis1 - redis2 - redis3 - redis4 - redis5 - redis6 privileged: true environment: TZ: Asia/Shanghai network_mode: host redis1: # 鏡像名 image: redis # 重啟策略 失敗后總是重啟 restart: always # 主機名 hostname: redis1 # 容器名 container_name: redis1 # 是否有權限 true privileged: true # 網絡模式 host表示與宿主機使用相同的IP,docker中的IP發生變化 network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis1/data:/data - ${BASE_DIR}/redis1/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis1/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis2: image: redis restart: always hostname: redis2 container_name: redis2 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis2/data:/data - ${BASE_DIR}/redis2/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis2/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis3: image: redis restart: always hostname: redis3 container_name: redis3 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis3/data:/data - ${BASE_DIR}/redis3/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis3/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis4: image: redis restart: always hostname: redis4 container_name: redis4 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis4/data:/data - ${BASE_DIR}/redis4/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis4/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis5: image: redis restart: always hostname: redis5 container_name: redis5 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis5/data:/data - ${BASE_DIR}/redis5/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis5/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ] redis6: image: redis restart: always hostname: redis6 container_name: redis6 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis6/data:/data - ${BASE_DIR}/redis6/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis6/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ]
redis.conf配置文件
protected-mode yes port 7001 tcp-backlog 511 timeout 0 tcp-keepalive 300 daemonize no supervised no pidfile /var/run/redis_7001.pid loglevel verbose databases 16 always-show-logo yes save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir ./ masterauth abcAbc123. replica-serve-stale-data yes replica-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no replica-priority 100 requirepass abcAbc123. lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no appendonly yes appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes aof-use-rdb-preamble yes lua-time-limit 5000 cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000 cluster-replica-validity-factor 10 slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 notify-keyspace-events "" 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 stream-node-max-bytes 4096 stream-node-max-entries 100 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 dynamic-hz yes aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes
上面時原始的redis.conf去除了注釋后,并修改了部分屬性,修改部分如下:
# 端口號 port 7001 # 進程ID保存文件 pidfile /var/run/redis_7001.pid # 向主服務器認證的密碼 masterauth abcAbc123. # 服務器自己的密碼 與向主服務器認證密碼保持一致 requirepass abcAbc123. # 開啟appendaof模式 appendonly yes # 開啟集群 cluster-enabled yes # 集群節點配置文件名稱 cluster-config-file nodes-7001.conf
啟動集群
cd /opt/redis/docker_redis_cluster && docker-compose up -d
測試集群
# 進入容器 docker exec -it redis1 /bin/bash # 使用集群方式連接Redis redis-cli -h 162.14.74.11 -p 7001 -c # 使用密碼認證 162.14.74.11:7001> auth abcAbc123. OK # 使用cluster nodes查詢集群節點 162.14.74.11:7001> cluster nodes ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663164275000 1 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663164276864 2 connected 5461-10922 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663164274000 1 connected 0-5460 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663164275861 2 connected 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663164275000 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663164273856 3 connected 10923-16383
使用Docker方式實現集群,其實跟手動實現集群是一樣的,只是使用docker-compose一鍵實現了該操作而已。
2.3、Redis客戶端分片與重定向
不同節點分組服務于相互無交集的分片(sharding),Redis Cluster不存在單獨的proxy或配置服務器,所以需要將客戶端路由到目標的分片。
Redis Cluster的客戶端相比單機Redis需要具備路由語義的識別能力,且具備一定的路由緩存能力。
2.3.1、moved重定向
我們直到,Redis集群數據時存儲在各個分片上的,如果我們連接某一個幾點,但是數據沒有在該節點上,將會被重定向到其他節點獲取數據。流程圖如下:
流程圖
流程說明
- 1、每個節點都共享RedisCluster中槽和集群中對應節點的關系(元數據共享)
- 2、客戶端向RedisCluster的任意節點發送命令,接收命令的節點會根據CRC16規則進行hash運算與16384取余,計算自己的槽和對應節點(計算所在槽和節點)
- 3、如果保存數據的槽被分配給當前節點,則去槽中執行命令,并把命令執行結果返回給客戶端(數據就在本節點,執行就返回)
- 4、如果保存數據的槽不在當前節點的管理范圍內,則向客戶端返回moved重定向異常(不在,返回moved重定向標識)
- 5、客戶端接收到節點返回的結果,如果是moved異常,則從moved異常中獲取目標節點的信息(客戶端收到moved重定向)
- 6、客戶端向目標節點發送命令,獲取命令執行結果(連接目標節點執行命令返回)
命令實現
# 在7001上面設置獲取key root@redis1:/data# redis-cli -h 162.14.74.11 -p 7001 -c 162.14.74.11:7001> get name # 會經過認證 (error) NOAUTH Authentication required. 162.14.74.11:7001> auth abcAbc123. OK # 獲取 162.14.74.11:7001> get name # 獲取時發現在7002上,重定向到7002, 但是開啟了認證 所有沒有獲取到 -> Redirected to slot [5798] located at 162.14.74.11:7002 (error) NOAUTH Authentication required. # 進行認證 162.14.74.11:7002> auth abcAbc123. OK # 重新獲取則成功 162.14.74.11:7002> get name "zhangsan"
2.3.2、ask重定向
在對集群進行擴容和縮容時,需要對槽及槽中數據進行遷移
當客戶端向某個節點發送命令,節點向客戶端返回moved異常,告訴客戶端數據對應的槽的節點信息
如果此時正在進行集群擴展或者縮空操作,當客戶端向正確的節點發送命令時,槽及槽中數據已經被遷移到別的節點了,就會返回ask,這就是ask重定向機制。
流程圖
流程說明
- 1、客戶端向目標節點發送命令,目標節點中的槽已經遷移支別的節點上了,此時目標節點會返回ask重定向給客戶端。
- 2、客戶端向新的節點發送Asking命令給新的節點,然后再次向新節點發送命令
- 3、新節點執行命令,把命令執行結果返回給客戶端
- 1、moved重定向,槽已完成了遷移工作
- 2、ask重定向:槽正在遷移的過程中
Redis時可以實現動態擴縮容的,Redis擴容就是向Redis中添加節點
防火墻開通端口
firewall-cmd --zone=public --add-port=7007/tcp --permanent firewall-cmd --zone=public --add-port=17007/tcp --permanent systemctl restart firewalld.service
處理這個,還需要開啟云服務器的安全組規則。能夠telnet通才可以。
[root@VM-0-5-centos docker_redis_cluster]# telnet 162.14.74.11 7007 Trying 162.14.74.11... Connected to 162.14.74.11. Escape character is '^]'.
編寫增加節點的docker-compose.yml
version: '2' services: redis7: image: redis restart: always hostname: redis7 container_name: redis7 privileged: true network_mode: host environment: TZ: Asia/Shanghai volumes: - ${BASE_DIR}/redis7/data:/data - ${BASE_DIR}/redis7/conf/redis.conf:/etc/redis/redis.conf - ${BASE_DIR}/redis7/logs:/logs command: [ "redis-server","/etc/redis/redis.conf" ]
執行文件
docker-compose -f addNode.yml up -d
加入集群
在任意一臺機器執行如下命令
# 注意 如果設置了密碼 加入節點時使用-a指定密碼 redis-cli --cluster add-node 162.14.74.11:7007 162.14.74.11:7001 -a abcAbc123.
執行結果
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Adding node 162.14.74.11:7007 to cluster 162.14.74.11:7001 >>> Performing Cluster Check (using node 162.14.74.11:7001) M: d77367d7830b4503980a14e379cb06a271906787 162.14.74.11:7001 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005 slots: (0 slots) slave replicates d77367d7830b4503980a14e379cb06a271906787 M: 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006 slots: (0 slots) slave replicates 5a92ba294e35f1d61e8091216c59c2fa44adfb9d S: 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004 slots: (0 slots) slave replicates 851c1b20f0616b60f172837e7e4bce792397d8ac M: 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003 slots:[10923-16383] (5461 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.
同理,使用cluster nodes查看節點信息
ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663166631000 1 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663166631175 2 connected 5461-10922 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663166628000 1 connected 0-5460 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663166630173 2 connected d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007@17007 master - 0 1663166632178 0 connected 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663166631000 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663166630000 3 connected 10923-16383
可以看到,節點已經加入成功了,但是發現不像其他節點有一個connected 0-5460。這是因為還沒有分配hash操作,節點上沒有數據。
2.4.1、Redis數據遷移+重新分配hash槽
添加完主節點需要對主節點進行hash槽分配,這樣該主節才可以存儲數據。
Redis數據遷移原理
- 1、向節點B發送狀態變更命令,將B的對應slot狀態置為importing。
- 2、向節點A發送狀態變更命令,將A對應的slot狀態置為migrating。
- 3、向A發送migrate命令,告知A將要遷移的slot對應的key遷移到B。
- 4、當所有key遷移完成后,cluster setslot重新設置槽位。
# 指定rehash重新分槽的命令 redis-cli --cluster reshard 162.14.74.11:7007 -a abcAbc123. # 輸入hash槽數量,表示要分配hash槽給目標節點 How many slots do you want to move (from 1 to 16384)? 輸入3000 # 輸入接收槽的結點id 也就是7007節點的id 可以在前面的cluster nodes中獲取 d5004e076572b8fab64c8cc4473c298396b78823 What is the receiving node ID? d5004e076572b8fab64c8cc4473c298396b78823 # 輸入源結點id,這里輸入all表示從其他所有節點中都分一點槽出來 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 # 輸入yes開始移動槽到目標結點id Do you want to proceed with the proposed reshard plan (yes/no)? yes # 這些就是遷移日志 還有很多 只復制了兩行 Moving slot 195 from 162.14.74.11:7001 to 162.14.74.11:7007: Moving slot 196 from 162.14.74.11:7001 to 162.14.74.11:7007:
使用cluster nodes查看
162.14.74.11:7001> cluster nodes ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663167613059 7 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663167610000 2 connected 7687-10922 # 7001有兩端槽 因為之前遷移錯誤 遷移到7001去了 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663167612000 7 connected 1550-6961 10923-12421 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663167611053 2 connected # 這里7007有i狼三段槽,從7001-7003都遷移了部分過來 d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007@17007 master - 0 1663167611000 8 connected 0-1549 6962-7686 12422-13146 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663167612055 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663167608044 3 connected 13147-16383
2.4.2、Redis添加從節點
添加7008從結點,將7008作為7007的從結點
放行防火墻和云服務器安全組
firewall-cmd --zone=public --add-port=7008/tcp --permanent firewall-cmd --zone=public --add-port=17008/tcp --permanent systemctl restart firewalld.service
安全組略,需要修改騰訊云上的端口規則。
添加從節點
# redis-cli --cluster add-node 新節點的ip和端口 舊節點ip和端口 --cluster-slave --cluster-master-id 主節點id -a 密碼 redis-cli --cluster add-node 162.14.74.11:7008 162.14.74.11:7007 --cluster-slave --cluster-master-id d5004e076572b8fab64c8cc4473c298396b78823 -a abcAbc123.
執行結果
root@redis1:/data# redis-cli --cluster add-node 162.14.74.11:7008 162.14.74.11:7007 --cluster-slave --cluster-master-id d5004e076572b8fab64c8cc4473c298396b78823 -a abcAbc123. Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. >>> Adding node 162.14.74.11:7008 to cluster 162.14.74.11:7007 >>> Performing Cluster Check (using node 162.14.74.11:7007) M: d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007 slots:[0-1549],[6962-7686],[12422-13146] (3000 slots) master S: ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005 slots: (0 slots) slave replicates d77367d7830b4503980a14e379cb06a271906787 S: 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004 slots: (0 slots) slave replicates 851c1b20f0616b60f172837e7e4bce792397d8ac M: 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002 slots:[7687-10922] (3236 slots) master 1 additional replica(s) M: 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003 slots:[13147-16383] (3237 slots) master 1 additional replica(s) S: 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006 slots: (0 slots) slave replicates 5a92ba294e35f1d61e8091216c59c2fa44adfb9d M: d77367d7830b4503980a14e379cb06a271906787 162.14.74.11:7001 slots:[1550-6961],[10923-12421] (6911 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. >>> Send CLUSTER MEET to node 162.14.74.11:7008 to make it join the cluster. Waiting for the cluster to join >>> Configure node as replica of 162.14.74.11:7007. [OK] New node added correctly.
使用cluster nodes查看
64b8c5798f880927b81ca09d8fc21ab2402767a5 162.14.74.11:7008@17008 slave d5004e076572b8fab64c8cc4473c298396b78823 0 1663168579000 8 connected ef24f98f9d811539ed3aa7ebd58b42c02ae11c1a 162.14.74.11:7005@17005 slave d77367d7830b4503980a14e379cb06a271906787 0 1663168577000 7 connected 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 162.14.74.11:7002@17002 master - 0 1663168579327 2 connected 7687-10922 d77367d7830b4503980a14e379cb06a271906787 10.0.0.5:7001@17001 myself,master - 0 1663168578000 7 connected 1550-6961 10923-12421 448b264edf1f697128395d8b6a656129024e2b55 162.14.74.11:7006@17006 slave 5a92ba294e35f1d61e8091216c59c2fa44adfb9d 0 1663168580330 2 connected d5004e076572b8fab64c8cc4473c298396b78823 162.14.74.11:7007@17007 master - 0 1663168579000 8 connected 0-1549 6962-7686 12422-13146 841ea8438a496f02c3866fa71fc9d4271b94e946 162.14.74.11:7004@17004 slave 851c1b20f0616b60f172837e7e4bce792397d8ac 0 1663168579000 3 connected 851c1b20f0616b60f172837e7e4bce792397d8ac 162.14.74.11:7003@17003 master - 0 1663168578000 3 connected 13147-16383
2.4.3、Redis縮容
原理與擴容一樣
redis-cli --cluster del-node 162.14.74.11:7008 64b8c5798f880927b81ca09d8fc21ab2402767a5 -a abcAbc123.
2.5、Redis故障轉移(failover)
故障檢測
集群中的每個節點都會定期地(每秒)向集群中的其他節點發送PING消息
如果在一定時間內(cluster-node-timeout),發送ping的節點A沒有收到某節點B的pong回應,則A將B標識為pfail。
A在后續發送ping時,會帶上B的pfail信息, 通知給其他節點。
如果B被標記為pfail的個數大于集群主節點個數的一半(N/2 + 1)時,B會被標記為fail,A向整個集群廣播,該節點已經下線。其他節點收到廣播,標記B為fail。
從節點選舉
使用raft算法,每個從節點,都根據自己對master復制數據的offset,來設置一個選舉時間,offset越大(復制數據越多)的從節點,選舉時間越靠前,優先進行選舉。
slave通過向其他master發送FAILVOER_AUTH_REQUEST消息發起競選,master收到后回復FAILOVER_AUTH_ACK消息告知是否同意。
slave發送FAILOVER_AUTH_REQUEST前會將currentEpoch自增,并將最新的Epoch帶入到FAILOVER_AUTH_REQUEST消息中,如果自己未投過票,則回復同意,否則回復拒絕。
所有的Master開始slave選舉投票,給要進行選舉的slave進行投票,如果大部分master node(N/2 + 1)都投票給了某個從節點,那么選舉通過,那個從節點可以切換成master。
RedisCluster失效的判定:
- 1、集群中半數以上的主節點都宕機(無法投票)
- 2、宕機的主節點的從節點也宕機了(slot槽分配不連續)
變更通知
當slave收到過半的master同意時,會成為新的master。此時會以最新的Epoch通過PONG消息廣播自己成為master,讓Cluster的其他節點盡快的更新拓撲結構(node.conf)
主從切換
從節點通過選舉自動切換。
人工處理手動切換
- 1、向從節點發送cluster failover命令
- 2、從節點告知其主節點要進行手動切換(CLUSTERMSG_TYPE_MFSTART)
- 3、主節點會阻塞所有客戶端命令的執行(10s)
- 4、從節點從主節點的ping包中獲得主節點的復制偏移量
- 5、從節點復制達到偏移量,發起選舉、統計選票、贏得選舉、升級為主節點并更新配置
- 6、切換完成后,原主節點向所有客戶端發送moved指令重定向到新的主節點
如果主節點下線了,則采用cluster failover force或cluster failover takeover進行強制切換。
副本漂移
我們知道在一主一從的情況下,如果主從同時掛了,那整個集群就掛了。
為了避免這種情況我們可以做一主多從,但這樣成本就增加了。
Redis提供了一種方法叫副本漂移,這種方法既能提高集群的可靠性又不用增加太多的從機。
Master1宕機,則Slaver11提升為新的Master1
集群檢測到新的Master1是單點的(無從機)
集群從擁有最多的從機的節點組(Master3)中,選擇節點名稱字母順序最小的從機(Slaver31)漂移到單點的主從節點組(Master1)。具體流程如下(以上圖為例):
- 1、將Slaver31的從機記錄從Master3中刪除
- 2、將Slaver31的的主機改為Master1
- 3、在Master1中添加Slaver31為從節點
- 4、將Slaver31的復制源改為Master1
- 5、通過ping包將信息同步到集群的其他節點
雖然Redis集群操作很簡單,但是其內的原理涉及到的知識點非常全面,了解其原理,可以更好的應對線上線下的問題。