Redis 分片集群的實現(xiàn)
1 搭建分片集群
主從和哨兵可以解決高可用、高并發(fā)讀的問題。但是依然有兩個問題沒有解決:
- 海量數(shù)據(jù)存儲問題,單個Redis節(jié)點對于數(shù)據(jù)的存儲量是有上限的
- 高并發(fā)寫的問題,高并發(fā)讀的問題我們可以用主從集群來解決,那高并發(fā)寫的問題又該怎樣解決呢
針對上述問題,我們可以搭建Redis的分片集群,如圖所示:

Redis的分片集群具有以下特征:
- 集群中有多個master,每個master保存不同數(shù)據(jù)
- 每個master都可以有多個slave節(jié)點
- master之間通過ping監(jiān)測彼此健康狀態(tài)(可以取代哨兵機制)
- 客戶端請求可以訪問集群任意節(jié)點,最終都會被轉(zhuǎn)發(fā)到正確節(jié)點
接下來我們可以動手來搭建一個Redis分片集群
1.1 集群結(jié)構(gòu)
分片集群需要的節(jié)點數(shù)量較多,這里我們搭建一個最小的分片集群,包含3個master節(jié)點,每個master包含一個slave節(jié)點,結(jié)構(gòu)如下:

這里我們會在同一臺虛擬機中開啟6個redis實例,模擬分片集群,信息如下:
| IP | PORT | 角色 |
|---|---|---|
| 192.168.211.100 | 7001 | master |
| 192.168.211.100 | 7002 | master |
| 192.168.211.100 | 7003 | master |
| 192.168.211.100 | 8001 | slave |
| 192.168.211.100 | 8002 | slave |
| 192.168.211.100 | 8003 | slave |
1.2 準(zhǔn)備實例和配置
這里我的redis安裝目錄為/usr/local/redis-6.2.6,以下操作將以此目錄進行參考,額外需要注意的是,以下操作都是在redis沒有設(shè)置密碼的情況下進行的,如果你的redis設(shè)置了密碼,那么按照以下步驟進行就會出問題。
1)創(chuàng)建出7001、7002、7003、8001、8002、8003目錄
# 進入/local目錄 cd /usr/local # 創(chuàng)建目錄 mkdir 7001 7002 7003 8001 8002 8003
2)在/usr/local下準(zhǔn)備一個新的redis.conf文件,內(nèi)容如下:
port 6379 # 開啟集群功能 cluster-enabled yes # 集群的配置文件名稱,不需要我們創(chuàng)建,由redis自己維護 cluster-config-file /usr/local/6379/nodes.conf # 節(jié)點心跳失敗的超時時間 cluster-node-timeout 5000 # 持久化文件存放目錄 dir /usr/local/6379 # 綁定地址 bind 0.0.0.0 # 讓redis后臺運行 daemonize yes # 注冊的實例ip replica-announce-ip 192.168.211.100 # 保護模式 protected-mode no # 數(shù)據(jù)庫數(shù)量 databases 1 # 日志 logfile /usr/local/6379/run.log
3)將這個文件拷貝到每個目錄下:
# 進入/local目錄 cd /usr/local # 執(zhí)行拷貝 echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf
4)修改每個目錄下的redis.conf,將其中的6379修改為與所在目錄一致:
# 進入/local目錄
cd /usr/local
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf
1.3 啟動
因為已經(jīng)配置了后臺啟動模式,所以可以直接啟動服務(wù):
# 進入/usr/local目錄
cd /usr/local
# 一鍵啟動所有服務(wù)
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf
通過ps查看狀態(tài):
ps -ef | grep redis
發(fā)現(xiàn)服務(wù)都已經(jīng)正常啟動:

如果要關(guān)閉所有進程,可以執(zhí)行命令:
ps -ef | grep redis | awk '{print $2}' | xargs kill或者(推薦這種方式):
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} -t redis-cli -p {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} shutdown1.4 創(chuàng)建集群
雖然服務(wù)啟動了,但是目前每個服務(wù)之間都是獨立的,沒有任何關(guān)聯(lián)。我們需要執(zhí)行以下命令來創(chuàng)建集群,注意,以下命令需要你的redis版本在5.0之后:
redis-cli --cluster create --cluster-replicas 1 192.168.211.100:7001 192.168.211.100:7002 192.168.211.100:7003 192.168.211.100:8001 192.168.211.100:8002 192.168.211.100:8003
命令說明:
redis-cli --cluster:代表集群操作命令create:代表創(chuàng)建集群--cluster-replicas 1:指定集群中每個master的副本個數(shù)為1,也就是說一個master只有一個slave,此時節(jié)點總數(shù) ÷ (replicas + 1)得到的就是master的數(shù)量。因此節(jié)點列表中的前n個就是master,其它節(jié)點都是slave節(jié)點,在創(chuàng)建集群時這些slave會被隨機分配給不同master
執(zhí)行上述命令之后,控制臺會列出當(dāng)前主從節(jié)點分配的結(jié)果,即將那些slave分別分配給哪些master,并詢問你是否同意,這里我們輸入'yes'即可

確定之后,集群開始創(chuàng)建

通過命令可以查看集群狀態(tài):
redis-cli -p 7001 cluster nodes

1.5 測試
集群操作時,需要在redis-cli連接時帶上-c參數(shù)才可以
redis-cli -p 7001
通過觀察上述從節(jié)點的狀態(tài),我們發(fā)現(xiàn)7001的slave是8001,我們可以嘗試在7001里插入一個數(shù)字,再從8001里獲取

事實上,我們不僅可以從8001里獲取到num,也可以從其他slave甚至其他master里獲取到num:

而且我們發(fā)現(xiàn),當(dāng)我們試圖從其他節(jié)點獲取num時,最后都會跳轉(zhuǎn)到7001,為什么會這樣呢?這就涉及到我們即將講解的插槽原理
2 散列插槽
一個Redis分片集群有0~16383共16384個插槽(hash slot),這些插槽會被平均分給每一個master節(jié)點,一個master節(jié)點映射著一部分插槽,這一點在集群創(chuàng)建時的信息中可以看到

在分片集群中,數(shù)據(jù)key并不是與某個節(jié)點綁定,而是與插槽綁定。數(shù)據(jù)key與插槽是多對一的關(guān)系,redis會根據(jù)key的有效部分計算插槽值,然后將key放入對應(yīng)插槽,key的有效部分分兩種情況:
- 當(dāng)key中包含"{}“時,且”{}“中至少包含1個字符,”{}"中的部分是有效部分
- key中不包含"{}",整個key都是有效部分
舉個例子,假如key是num,那么插槽值就會根據(jù)num來計算,如果key是{itheima}num,那么插槽值就會根據(jù)itheima來計算。計算方式是利用CRC16算法得到一個hash值,然后對16384取余,得到的結(jié)果就是slot值。
如果當(dāng)前操作的key所在的插槽不屬于本節(jié)點,則會發(fā)生重定向,重定向的目標(biāo)是該插槽所屬的節(jié)點,這個節(jié)點一定是master,如果我們連接的節(jié)點為slave,則會直接進行重定向,因為slave是沒有插槽的。
針對上述幾點,演示如下:

如上圖所示,我們連接了7001,并試圖插入數(shù)據(jù)k1,這時redis需要去判斷k1所屬的插槽位置,由于key中不包含’{}',因此整個key都是有效部分,redis會對k1做hash運算然后對16384取余,得到的結(jié)果是12706,這也就是k1所在的插槽的位置,在當(dāng)前集群中,映射該插槽的節(jié)點是7003,此時就會發(fā)生重定向,我們也可以觀察到當(dāng)我們執(zhí)行完set k1 1命令之后,操作的端口已經(jīng)變成了7003。此時我們繼續(xù)在7003端口中進行操作,比如修改數(shù)據(jù)num的值,而num所在的插槽是2765,在當(dāng)前集群中,映射該插槽的節(jié)點是7001,因此當(dāng)我們執(zhí)行完set num 2命令之后,操作的端口又重新變成了7001
那么接下來讓我們思考兩個問題:
為什么數(shù)據(jù)key要與插槽綁定,而不是與節(jié)點綁定呢?
這是因為Redis的主節(jié)點有可能會出現(xiàn)宕機情況,也有可能由于集群伸縮而被刪除,當(dāng)節(jié)點刪除或者發(fā)生宕機時,節(jié)點上保存的數(shù)據(jù)也就丟失了,但如果數(shù)據(jù)綁定的是插槽而不是節(jié)點,那么當(dāng)出現(xiàn)上述情況時,就可以將故障節(jié)點的插槽轉(zhuǎn)移至存活節(jié)點上。這樣,數(shù)據(jù)跟插槽綁定,就永遠(yuǎn)都能夠找到數(shù)據(jù)所在位置。
如何將同一類數(shù)據(jù)固定的保存在一個插槽中
在業(yè)務(wù)開發(fā)中,同一類型的數(shù)據(jù)key最好是保存在一個插槽中,因為如果分散保存,在我們調(diào)用的時候就很可能出現(xiàn)重定向的情況,重定向是會消耗一部分性能的。如果我們希望將同一類型的數(shù)據(jù)key最好是保存在一個插槽中,可以為這些key帶上一個用’{}'包裹的固定前綴,比如{user}zs、{user}ls等等,因為我們之前說過,當(dāng)key中包含"{}“,且”{}“中至少包含1個字符時,”{}"中的部分是有效部分,redis會根據(jù)這一部分來計算插槽值,如果我們將同一類型的key都加上這類前綴,就能保證這些key在同一個插槽中了
3 集群伸縮
集群已經(jīng)創(chuàng)建了,那么如果我們想在集群中添加節(jié)點或刪除節(jié)點,又應(yīng)該怎么做呢?
redis-cli --cluster提供了很多操作集群的命令,可以通過下面方式查看:

其中就包括添加節(jié)點的命令:

假設(shè)現(xiàn)在有以下需求:向集群中添加一個新的master節(jié)點7004,并在這個節(jié)點中存儲 num = 10,執(zhí)行步驟如下:
3.1 創(chuàng)建節(jié)點并添加到集群
1)在/usr/local目錄下創(chuàng)建一個文件夾:
cd /usr/local mkdir 7004
2)拷貝配置文件:
cp redis.conf 7004
3)修改配置文件:
printf '%s\n' 7004 | xargs -I{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} -t sed -i 's/6379/{}/g' {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}/redis.conf4)啟動
redis-server 7004/redis.conf
接下來就需要將7004添加到集群中了,在執(zhí)行添加操作之前,我們先來了解以下添加節(jié)點的命令

添加節(jié)點首先需要以下幾個參數(shù):
new_host:new_port:指定新添加的節(jié)點的ip地址與端口號,這個沒什么好說的existing_host:existing_port:任意指定一個集群中已經(jīng)存在的節(jié)點的ip地址與端口號。因為集群中加入新節(jié)點是需要通知其他舊節(jié)點的,新節(jié)點只需要將自己的信息提供給集群中任意一個節(jié)點,那么整個集群就都能知道關(guān)于新節(jié)點的信息了cluster-slave:可選項,不指定則表示該節(jié)點是master,如果指定了則表示該節(jié)點是一個slavecluster-master-id <arg>:如果我們指定了cluster-slave,那么就需要通過該參數(shù)指定該節(jié)點的master的id
了解了該命令之后,接下來我們來執(zhí)行添加節(jié)點操作:
執(zhí)行命令:
redis-cli --cluster add-node 192.168.211.100:7004 192.168.211.100:7001
通過命令查看集群狀態(tài):
redis-cli -p 7001 cluster nodes
如圖,7004加入了集群,并且默認(rèn)是一個master節(jié)點:

但是,我們也可以看到7004是沒有插槽的,因為插槽已經(jīng)被其他master瓜分完畢了,因此沒有任何數(shù)據(jù)可以存儲到7004上,這時候我們就需要進行插槽的轉(zhuǎn)移,即將其他matser的插槽分出一部分給7004
3.2 轉(zhuǎn)移插槽
首先回歸需求本身,我們的需求是將num=10存儲在7004節(jié)點上,那么我們的目的就很明顯了,首先需要知道num存儲在哪個插槽上,然后將這個插槽轉(zhuǎn)移到7004上即可

如上圖所示,num的插槽為2765,該插槽目前是保存在7001上的,因此我們可以將0~3000的插槽從7001轉(zhuǎn)移到7004,轉(zhuǎn)移插槽的命令格式如下:

具體步驟如下:
1)輸入轉(zhuǎn)移插槽命令,這里我們需要轉(zhuǎn)移的插槽在7001上,因此需要指定7001的地址
redis-cli --cluster reshard 192.168.211.100:7001
2)系統(tǒng)會詢問我們要移動多少個插槽,這里我們輸入3000即可

3)系統(tǒng)接著詢問我們需要讓哪個節(jié)點來接收插槽,這里我們需要輸入7004的ID

4)接著系統(tǒng)會詢問我們要從哪些節(jié)點中移動這些插槽到7004

這里我們有三個選擇:
- all:代表全部,也就是三個節(jié)點各轉(zhuǎn)移一部分
- 具體的id:目標(biāo)節(jié)點的id
- done:表示結(jié)束
這里我們需要從7001中獲取插槽,因此填寫7001的id,然后輸入done表示結(jié)束

5)接下來會冒出一大串東西,并詢問我們是否確定要移動這些插槽,這里我們直接輸入yes即可

輸入yes之后,等待控制臺打印結(jié)束,插槽也就移動完畢了
6) 輸入以下命令查看插槽是否已經(jīng)移動到7004
redis-cli -p 7001 cluster nodes
很顯然,我們的目的已經(jīng)達(dá)成了

那么如果我們要刪除7004節(jié)點,又應(yīng)該怎樣做呢?這里筆者只給去具體思路,大家可以自行嘗試一下:
- 先將 7004 分配的插槽轉(zhuǎn)移至其他節(jié)點
- 執(zhí)行刪除節(jié)點命令
redis-cli --cluster del-node host:port node_id - 通過命令查詢結(jié)果
redis-cli -p 7001 cluster nodes
4 故障轉(zhuǎn)移
之前我們提到過,redis分片集群可以通過master之間的心跳監(jiān)測來監(jiān)測彼此之間的健康狀態(tài),從而取代哨兵。而我們也知道,哨兵的作用就是監(jiān)測master和slave的狀態(tài),當(dāng)認(rèn)為一個master客觀下線后,會從slave中選舉出一個新的master,現(xiàn)在讓我們來驗證一下redis分片集群是否具備這個功能。
首先集群的初始狀態(tài)是這樣的,如果狀態(tài)為connected則表示節(jié)點正常連接

其中7001、7002、7003、7004都是master,我們計劃讓7002宕機。
4.1.自動故障轉(zhuǎn)移
當(dāng)集群中有一個master宕機會發(fā)生什么呢?我們可以直接停止一個redis實例,例如7002:
redis-cli -p 7002 shutdown
1)首先是該實例與其它實例失去連接
2)然后是疑似宕機,7002的狀態(tài)變成了disconnected

3)最后是確定下線,將7002的一個slave提升為新的master,這里由于7002只有一個slave,即8002,因此8002被選為了新的master

4)接下來我們通過以下命令再次啟動7002節(jié)點
redis-server /usr/local/7002/redis.conf
當(dāng)7002再次啟動之后,它就已經(jīng)變?yōu)橐粋€slave節(jié)點了

上面這種叫自動故障轉(zhuǎn)移,但有的時候我們可能需要做手動故障轉(zhuǎn)移,比如當(dāng)某臺master機器比較老舊,需要升級時,我們就可以在這個集群中新增一個節(jié)點,讓這個節(jié)點成為取代原來的master成為新的master,這樣原來的master就會變成新master的一個slave,從而實現(xiàn)機器的無感知升級
4.2 手動故障轉(zhuǎn)移
我們可以在slave節(jié)點中使用cluster failover命令,這個命令會讓當(dāng)前slave節(jié)點的master暫時宕機,宕機期間會將自身的數(shù)據(jù)轉(zhuǎn)移給執(zhí)行命令的slave節(jié)點,宕機結(jié)束后,之前的master會變成slave,而執(zhí)行命令的slave會變成新的master。
其詳細(xì)流程如下:

當(dāng)slave執(zhí)行了cluster failover命令之后,就會向master節(jié)點發(fā)送節(jié)點替換通知,為了避免數(shù)據(jù)的丟失,master接收到slave節(jié)點發(fā)送過來的通知后,就會暫時拒絕來自客戶端的任何數(shù)據(jù)讀寫請求。然后,master會將自己當(dāng)前的offset返回給slave,slave接收到后會判斷自身數(shù)據(jù)中的offset與master的offset是否一致,如果不一致,則需要進行數(shù)據(jù)同步。由于 master 已經(jīng)拒絕了客戶端的所有請求,那么一旦 slave完成數(shù)據(jù)同步,也就表示slave與master之間數(shù)據(jù)是完全一致的。
數(shù)據(jù)同步結(jié)束之后,就會開始進行故障轉(zhuǎn)移,讓slave與master 進行角色互換,該slave成為新的master,而舊的master則轉(zhuǎn)變?yōu)橐粋€新的slave。轉(zhuǎn)移結(jié)束后,slave便會標(biāo)記自己為master,并向集群中每一個節(jié)點廣播故障轉(zhuǎn)移的結(jié)果。當(dāng)集群中節(jié)點收到廣播后,后續(xù)的所有交互便轉(zhuǎn)移至新的master。
這種failover命令可以指定三種模式:
缺?。耗J(rèn)的流程,如圖1~6歩,一般我們會選擇這個force:省略了對offset的一致性校驗,直接開始故障轉(zhuǎn)移takeover:直接執(zhí)行第5歩,忽略數(shù)據(jù)一致性、忽略master狀態(tài)和其它master的意見
接下來我們可以嘗試一下,在7002這個slave節(jié)點上執(zhí)行手動故障轉(zhuǎn)移,讓它重新奪回master地位,步驟如下:
1)利用redis-cli連接7002,并執(zhí)行cluster failover命令

2)通過redis-cli -p 7001 cluster nodes命令查看節(jié)點狀態(tài)

5 RedisTemplate訪問分片集群
我們只需要通過以下簡單的配置,就可以通過Java代碼訪問我們之前部署好的分片集群
1)在boot項目的pom文件中導(dǎo)入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2)在application.yml中指定sentinel相關(guān)信息:
spring:
redis:
cluster:
nodes: #指定分片集群中每一個節(jié)點信息
- 192.168.150.101:7001
- 192.168.150.101:7002
- 192.168.150.101:7003
- 192.168.150.101:8001
- 192.168.150.101:8002
- 192.168.150.101:8003
3)在項目的啟動類中,添加一個新的bean,這個bean是用來做Redis集群的讀寫分離的
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
bean中配置的信息是讀寫策略,包括四種可選項:
- MASTER:從master讀取
- MASTER_PREFERRED:優(yōu)先從master節(jié)點讀取,master不可用才讀取slave
- REPLICA:從slave節(jié)點讀取
- REPLICA _PREFERRED:優(yōu)先從slave節(jié)點讀取,所有的slave都不可用才讀取master
上述配置完畢之后,我們就可以正常使用RedisTemplate來對redis集群進行操作,如果集群中某個的master宕機了,集群就會自動選舉新的master,并將新master的信息發(fā)送給該Java程序,這樣Java程序就可以對新master進行寫操作而對其他節(jié)點進行讀操作了。而這一過程都是自動完成的,無需我們過多關(guān)注
到此這篇關(guān)于Redis 分片集群的實現(xiàn)的文章就介紹到這了,更多相關(guān)Redis 分片集群內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis動態(tài)熱點數(shù)據(jù)緩存策略設(shè)計
本文主要介紹了Redis動態(tài)熱點數(shù)據(jù)緩存策略設(shè)計,包括熱點數(shù)據(jù)識別、動態(tài)緩存、多級緩存、預(yù)加載機制、更新策略以及監(jiān)控告警等,具有一定的參考價值,感興趣的可以了解一下2025-01-01
淺析PHP分布式中Redis實現(xiàn)Session的方法
這篇文章主要介紹了PHP分布式中Redis實現(xiàn)Session的方法,文中詳細(xì)介紹了兩種方法的使用方法,并給出了測試的示例代碼,有需要的朋友可以參考借鑒,下面來一起看看吧,2016-12-12
redis中使用bloomfilter的白名單功能解決緩存穿透問題
本文主要介紹了redis中使用bloomfilter的白名單功能解決緩存穿透問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07

