首頁 > 軟體

Redis 分片叢集的實現

2023-02-01 18:03:15

1 搭建分片叢集

主從和哨兵可以解決高可用、高並行讀的問題。但是依然有兩個問題沒有解決:

  • 海量資料儲存問題,單個Redis節點對於資料的儲存量是有上限的
  • 高並行寫的問題,高並行讀的問題我們可以用主從叢集來解決,那高並行寫的問題又該怎樣解決呢

針對上述問題,我們可以搭建Redis的分片叢集,如圖所示:

Redis的分片叢集具有以下特徵:

  • 叢集中有多個master,每個master儲存不同資料
  • 每個master都可以有多個slave節點
  • master之間通過ping監測彼此健康狀態(可以取代哨兵機制)
  • 使用者端請求可以存取叢集任意節點,最終都會被轉發到正確節點

接下來我們可以動手來搭建一個Redis分片叢集

1.1 叢集結構

分片叢集需要的節點數量較多,這裡我們搭建一個最小的分片叢集,包含3個master節點,每個master包含一個slave節點,結構如下:

這裡我們會在同一臺虛擬機器器中開啟6個redis範例,模擬分片叢集,資訊如下:

IPPORT角色
192.168.211.1007001master
192.168.211.1007002master
192.168.211.1007003master
192.168.211.1008001slave
192.168.211.1008002slave
192.168.211.1008003slave

1.2 準備範例和設定

這裡我的redis安裝目錄為/usr/local/redis-6.2.6,以下操作將以此目錄進行參考,額外需要注意的是,以下操作都是在redis沒有設定密碼的情況下進行的,如果你的redis設定了密碼,那麼按照以下步驟進行就會出問題。

1)建立出7001、7002、7003、8001、8002、8003目錄

# 進入/local目錄
cd /usr/local
# 建立目錄
mkdir 7001 7002 7003 8001 8002 8003

2)在/usr/local下準備一個新的redis.conf檔案,內容如下:

port 6379
# 開啟叢集功能
cluster-enabled yes
# 叢集的組態檔名稱,不需要我們建立,由redis自己維護
cluster-config-file /usr/local/6379/nodes.conf
# 節點心跳失敗的超時時間
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
# 資料庫數量
databases 1
# 紀錄檔
logfile /usr/local/6379/run.log

3)將這個檔案拷貝到每個目錄下:

# 進入/local目錄
cd /usr/local
# 執行拷貝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

4)修改每個目錄下的redis.conf,將其中的6379修改為與所在目錄一致:

# 進入/local目錄
cd /usr/local
# 修改組態檔
printf '%sn' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

1.3 啟動

因為已經設定了後臺啟動模式,所以可以直接啟動服務:

# 進入/usr/local目錄
cd /usr/local
# 一鍵啟動所有服務
printf '%sn' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

通過ps檢視狀態:

ps -ef | grep redis

發現服務都已經正常啟動:

如果要關閉所有程序,可以執行命令:

ps -ef | grep redis | awk '{print $2}' | xargs kill

或者(推薦這種方式):

printf '%sn' 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-->} shutdown

1.4 建立叢集

雖然服務啟動了,但是目前每個服務之間都是獨立的,沒有任何關聯。我們需要執行以下命令來建立叢集,注意,以下命令需要你的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:代表建立叢集
  • --cluster-replicas 1 :指定叢集中每個master的副本個數為1,也就是說一個master只有一個slave,此時節點總數 ÷ (replicas + 1) 得到的就是master的數量。因此節點列表中的前n個就是master,其它節點都是slave節點,在建立叢集時這些slave會被隨機分配給不同master

執行上述命令之後,控制檯會列出當前主從節點分配的結果,即將那些slave分別分配給哪些master,並詢問你是否同意,這裡我們輸入'yes'即可

確定之後,叢集開始建立

通過命令可以檢視叢集狀態:

redis-cli -p 7001 cluster nodes

1.5 測試

叢集操作時,需要在redis-cli連線時帶上-c引數才可以

redis-cli -p 7001

通過觀察上述從節點的狀態,我們發現7001的slave是8001,我們可以嘗試在7001裡插入一個數位,再從8001裡獲取

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

而且我們發現,當我們試圖從其他節點獲取num時,最後都會跳轉到7001,為什麼會這樣呢?這就涉及到我們即將講解的插槽原理

2 雜湊插槽

一個Redis分片叢集有0~16383共16384個插槽(hash slot),這些插槽會被平均分給每一個master節點,一個master節點對映著一部分插槽,這一點在叢集建立時的資訊中可以看到

在分片叢集中,資料key並不是與某個節點繫結,而是與插槽繫結。資料key與插槽是多對一的關係,redis會根據key的有效部分計算插槽值,然後將key放入對應插槽,key的有效部分分兩種情況:

  • 當key中包含"{}“時,且”{}“中至少包含1個字元,”{}"中的部分是有效部分
  • key中不包含"{}",整個key都是有效部分

舉個例子,假如key是num,那麼插槽值就會根據num來計算,如果key是{itheima}num,那麼插槽值就會根據itheima來計算。計算方式是利用CRC16演演算法得到一個hash值,然後對16384取餘,得到的結果就是slot值。

如果當前操作的key所在的插槽不屬於本節點,則會發生重定向,重定向的目標是該插槽所屬的節點,這個節點一定是master,如果我們連線的節點為slave,則會直接進行重定向,因為slave是沒有插槽的。

針對上述幾點,演示如下:

如上圖所示,我們連線了7001,並試圖插入資料k1,這時redis需要去判斷k1所屬的插槽位置,由於key中不包含’{}',因此整個key都是有效部分,redis會對k1做hash運算然後對16384取餘,得到的結果是12706,這也就是k1所在的插槽的位置,在當前叢集中,對映該插槽的節點是7003,此時就會發生重定向,我們也可以觀察到當我們執行完set k1 1命令之後,操作的埠已經變成了7003。此時我們繼續在7003埠中進行操作,比如修改資料num的值,而num所在的插槽是2765,在當前叢集中,對映該插槽的節點是7001,因此當我們執行完set num 2命令之後,操作的埠又重新變成了7001

那麼接下來讓我們思考兩個問題:

為什麼資料key要與插槽繫結,而不是與節點繫結呢?

這是因為Redis的主節點有可能會出現宕機情況,也有可能由於叢集伸縮而被刪除,當節點刪除或者發生宕機時,節點上儲存的資料也就丟失了,但如果資料繫結的是插槽而不是節點,那麼當出現上述情況時,就可以將故障節點的插槽轉移至存活節點上。這樣,資料跟插槽繫結,就永遠都能夠找到資料所在位置。

如何將同一類資料固定的儲存在一個插槽中

在業務開發中,同一型別的資料key最好是儲存在一個插槽中,因為如果分散儲存,在我們呼叫的時候就很可能出現重定向的情況,重定向是會消耗一部分效能的。如果我們希望將同一型別的資料key最好是儲存在一個插槽中,可以為這些key帶上一個用’{}'包裹的固定字首,比如{user}zs、{user}ls等等,因為我們之前說過,當key中包含"{}“,且”{}“中至少包含1個字元時,”{}"中的部分是有效部分,redis會根據這一部分來計算插槽值,如果我們將同一型別的key都加上這類字首,就能保證這些key在同一個插槽中了

3 叢集伸縮

叢集已經建立了,那麼如果我們想在叢集中新增節點或刪除節點,又應該怎麼做呢?

redis-cli --cluster提供了很多操作叢集的命令,可以通過下面方式檢視:

其中就包括新增節點的命令:

假設現在有以下需求:向叢集中新增一個新的master節點7004,並在這個節點中儲存 num = 10,執行步驟如下:

3.1 建立節點並新增到叢集

1)在/usr/local目錄下建立一個資料夾:

cd /usr/local
mkdir 7004

2)拷貝組態檔:

cp redis.conf 7004

3)修改組態檔:

printf '%sn' 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.conf

4)啟動

redis-server 7004/redis.conf

接下來就需要將7004新增到叢集中了,在執行新增操作之前,我們先來了解以下新增節點的命令

新增節點首先需要以下幾個引數:

  • new_host:new_port :指定新新增的節點的ip地址與埠號,這個沒什麼好說的
  • existing_host:existing_port:任意指定一個叢集中已經存在的節點的ip地址與埠號。因為叢集中加入新節點是需要通知其他舊節點的,新節點只需要將自己的資訊提供給叢集中任意一個節點,那麼整個叢集就都能知道關於新節點的資訊了
  • cluster-slave:可選項,不指定則表示該節點是master,如果指定了則表示該節點是一個slave
  • cluster-master-id <arg>:如果我們指定了cluster-slave,那麼就需要通過該引數指定該節點的master的id

瞭解了該命令之後,接下來我們來執行新增節點操作:

執行命令:

redis-cli --cluster add-node 192.168.211.100:7004 192.168.211.100:7001

通過命令檢視叢集狀態:

redis-cli -p 7001 cluster nodes

如圖,7004加入了叢集,並且預設是一個master節點:

但是,我們也可以看到7004是沒有插槽的,因為插槽已經被其他master瓜分完畢了,因此沒有任何資料可以儲存到7004上,這時候我們就需要進行插槽的轉移,即將其他matser的插槽分出一部分給7004

3.2 轉移插槽

首先回歸需求本身,我們的需求是將num=10儲存在7004節點上,那麼我們的目的就很明顯了,首先需要知道num儲存在哪個插槽上,然後將這個插槽轉移到7004上即可

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

具體步驟如下:

1)輸入轉移插槽命令,這裡我們需要轉移的插槽在7001上,因此需要指定7001的地址

redis-cli --cluster reshard 192.168.211.100:7001

2)系統會詢問我們要移動多少個插槽,這裡我們輸入3000即可

3)系統接著詢問我們需要讓哪個節點來接收插槽,這裡我們需要輸入7004的ID

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

這裡我們有三個選擇:

  • all:代表全部,也就是三個節點各轉移一部分
  • 具體的id:目標節點的id
  • done:表示結束

這裡我們需要從7001中獲取插槽,因此填寫7001的id,然後輸入done表示結束

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

輸入yes之後,等待控制檯列印結束,插槽也就移動完畢了

6) 輸入以下命令檢視插槽是否已經移動到7004

redis-cli -p 7001 cluster nodes

很顯然,我們的目的已經達成了

那麼如果我們要刪除7004節點,又應該怎樣做呢?這裡筆者只給去具體思路,大家可以自行嘗試一下:

  • 先將 7004 分配的插槽轉移至其他節點
  • 執行刪除節點命令redis-cli --cluster del-node host:port node_id
  • 通過命令查詢結果redis-cli -p 7001 cluster nodes

4 故障轉移

之前我們提到過,redis分片叢集可以通過master之間的心跳監測來監測彼此之間的健康狀態,從而取代哨兵。而我們也知道,哨兵的作用就是監測master和slave的狀態,當認為一個master客觀下線後,會從slave中選舉出一個新的master,現在讓我們來驗證一下redis分片叢集是否具備這個功能。

首先叢集的初始狀態是這樣的,如果狀態為connected則表示節點正常連線

其中7001、7002、7003、7004都是master,我們計劃讓7002宕機。

4.1.自動故障轉移

當叢集中有一個master宕機會發生什麼呢?我們可以直接停止一個redis範例,例如7002:

redis-cli -p 7002 shutdown

1)首先是該範例與其它範例失去連線

2)然後是疑似宕機,7002的狀態變成了disconnected

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

4)接下來我們通過以下命令再次啟動7002節點

redis-server /usr/local/7002/redis.conf

當7002再次啟動之後,它就已經變為一個slave節點了

上面這種叫自動故障轉移,但有的時候我們可能需要做手動故障轉移,比如當某臺master機器比較老舊,需要升級時,我們就可以在這個叢集中新增一個節點,讓這個節點成為取代原來的master成為新的master,這樣原來的master就會變成新master的一個slave,從而實現機器的無感知升級

4.2 手動故障轉移

我們可以在slave節點中使用cluster failover命令,這個命令會讓當前slave節點的master暫時宕機,宕機期間會將自身的資料轉移給執行命令的slave節點,宕機結束後,之前的master會變成slave,而執行命令的slave會變成新的master。

其詳細流程如下:

當slave執行了cluster failover命令之後,就會向master節點傳送節點替換通知,為了避免資料的丟失,master接收到slave節點傳送過來的通知後,就會暫時拒絕來自使用者端的任何資料讀寫請求。然後,master會將自己當前的offset返回給slave,slave接收到後會判斷自身資料中的offset與master的offset是否一致,如果不一致,則需要進行資料同步。由於 master 已經拒絕了使用者端的所有請求,那麼一旦 slave完成資料同步,也就表示slave與master之間資料是完全一致的。

資料同步結束之後,就會開始進行故障轉移,讓slave與master 進行角色互換,該slave成為新的master,而舊的master則轉變為一個新的slave。轉移結束後,slave便會標記自己為master,並向叢集中每一個節點廣播故障轉移的結果。當叢集中節點收到廣播後,後續的所有互動便轉移至新的master。

這種failover命令可以指定三種模式:

預設:預設的流程,如圖1~6歩,一般我們會選擇這個force:省略了對offset的一致性校驗,直接開始故障轉移takeover:直接執行第5歩,忽略資料一致性、忽略master狀態和其它master的意見

接下來我們可以嘗試一下,在7002這個slave節點上執行手動故障轉移,讓它重新奪回master地位,步驟如下:

1)利用redis-cli連線7002,並執行cluster failover命令

2)通過redis-cli -p 7001 cluster nodes命令檢視節點狀態

5 RedisTemplate存取分片叢集

我們只需要通過以下簡單的設定,就可以通過Java程式碼存取我們之前部署好的分片叢集

1)在boot專案的pom檔案中匯入依賴

 <dependency>
 	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

2)在application.yml中指定sentinel相關資訊:

spring:
  redis:
    cluster:
      nodes: #指定分片叢集中每一個節點資訊
        - 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:優先從master節點讀取,master不可用才讀取slave
  • REPLICA:從slave節點讀取
  • REPLICA _PREFERRED:優先從slave節點讀取,所有的slave都不可用才讀取master

上述設定完畢之後,我們就可以正常使用RedisTemplate來對redis叢集進行操作,如果叢集中某個的master宕機了,叢集就會自動選舉新的master,並將新master的資訊傳送給該Java程式,這樣Java程式就可以對新master進行寫操作而對其他節點進行讀操作了。而這一過程都是自動完成的,無需我們過多關注

到此這篇關於Redis 分片叢集的實現的文章就介紹到這了,更多相關Redis 分片叢集內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com