天天看點

深入學習Redis(5):叢集

Redis叢集實作了較為完善的高可用方案。本文将詳細介紹叢集,主要内容包括:叢集的作用;叢集的搭建以及設計方案;叢集的基本原理;用戶端通路叢集的方法;以及其他實踐中需要的叢集知識。

在前面的文章中,已經介紹了Redis的幾種高可用技術:持久化、主從複制和哨兵,但這些方案仍有不足,其中最主要的問題是存儲能力受單機限制,以及無法實作寫操作的負載均衡。

Redis叢集解決了上述問題,實作了較為完善的高可用方案。本文将詳細介紹叢集,主要内容包括:叢集的作用;叢集的搭建方法及設計方案;叢集的基本原理;用戶端通路叢集的方法;以及其他實踐中需要的叢集知識(叢集擴容、故障轉移、參數優化等)。

深入學習Redis(1):Redis記憶體模型

深入學習Redis(2):持久化

深入學習Redis(3):主從複制

深入學習Redis(4):哨兵

深入學習Redis(5):叢集

一、叢集的作用

二、叢集的搭建

     1. 執行Redis指令搭建叢集

     2. 使用Ruby腳本搭建叢集

     3. 叢集方案設計

三、叢集的基本原理

     1. 資料分區方案

     2. 節點通信機制

     3. 資料結構

     4. 叢集指令的實作

四、用戶端通路叢集

     1. redis-cli

     2. Smart用戶端

五、實踐須知

     1. 叢集伸縮

     2. 故障轉移

     3. 叢集的限制及應對方法

     4. Hash Tag

     5. 參數優化

     6. redis-trib.rb

參考文獻

叢集,即Redis Cluster,是Redis 3.0開始引入的分布式存儲方案。

叢集由多個節點(Node)組成,Redis的資料分布在這些節點中。叢集中的節點分為主節點和從節點:隻有主節點負責讀寫請求和叢集資訊的維護;從節點隻進行主節點資料和狀态資訊的複制。

叢集的作用,可以歸納為兩點:

1、資料分區:資料分區(或稱資料分片)是叢集最核心的功能。

叢集将資料分散到多個節點,一方面突破了Redis單機記憶體大小的限制,存儲容量大大增加;另一方面每個主節點都可以對外提供讀服務和寫服務,大大提高了叢集的響應能力。

Redis單機記憶體大小受限問題,在介紹持久化和主從複制時都有提及;例如,如果單機記憶體太大,bgsave和bgrewriteaof的fork操作可能導緻主程序阻塞,主從環境下主機切換時可能導緻從節點長時間無法提供服務,全量複制階段主節點的複制緩沖區可能溢出……。

2、高可用:叢集支援主從複制和主節點的自動故障轉移(與哨兵類似);當任一節點發生故障時,叢集仍然可以對外提供服務。

本文内容基于Redis 3.0.6。

這一部分我們将搭建一個簡單的叢集:共6個節點,3主3從。友善起見:所有節點在同一台伺服器上,以端口号進行區分;配置從簡。3個主節點端口号:7000/7001/7002,對應的從節點端口号:8000/8001/8002。

叢集的搭建有兩種方式:(1)手動執行Redis指令,一步步完成搭建;(2)使用Ruby腳本搭建。二者搭建的原理是一樣的,隻是Ruby腳本将Redis指令進行了打包封裝;在實際應用中推薦使用腳本方式,簡單快捷不容易出錯。下面分别介紹這兩種方式。

叢集的搭建可以分為四步:(1)啟動節點:将節點以叢集模式啟動,此時節點是獨立的,并沒有建立聯系;(2)節點握手:讓獨立的節點連成一個網絡;(3)配置設定槽:将16384個槽配置設定給主節點;(4)指定主從關系:為從節點指定主節點。

實際上,前三步完成後叢集便可以對外提供服務;但指定從節點後,叢集才能夠提供真正高可用的服務。

叢集節點的啟動仍然是使用redis-server指令,但需要使用叢集模式啟動。下面是7000節點的配置檔案(隻列出了節點正常工作關鍵配置,其他配置(如開啟AOF)可以參照單機節點進行):

其中的cluster-enabled和cluster-config-file是與叢集相關的配置。

cluster-enabled yes:Redis執行個體可以分為單機模式(standalone)和叢集模式(cluster);cluster-enabled yes可以啟動叢集模式。在單機模式下啟動的Redis執行個體,如果執行info server指令,可以發現redis_mode一項為standalone,如下圖所示:

深入學習Redis(5):叢集

叢集模式下的節點,其redis_mode為cluster,如下圖所示:

深入學習Redis(5):叢集

cluster-config-file:該參數指定了叢集配置檔案的位置。每個節點在運作過程中,會維護一份叢集配置檔案;每當叢集資訊發生變化時(如增減節點),叢集内所有節點會将最新資訊更新到該配置檔案;當節點重新開機後,會重新讀取該配置檔案,擷取叢集資訊,可以友善的重新加入到叢集中。也就是說,當Redis節點以叢集模式啟動時,會首先尋找是否有叢集配置檔案,如果有則使用檔案中的配置啟動,如果沒有,則初始化配置并将配置儲存到檔案中。叢集配置檔案由Redis節點維護,不需要人工修改。

編輯好配置檔案後,使用redis-server指令啟動該節點:

節點啟動以後,通過cluster nodes指令可以檢視節點的情況,如下圖所示。

深入學習Redis(5):叢集

其中傳回值第一項表示節點id,由40個16進制字元串組成,節點id與 主從複制 一文中提到的runId不同:Redis每次啟動runId都會重新建立,但是節點id隻在叢集初始化時建立一次,然後儲存到叢集配置檔案中,以後節點重新啟動時會直接在叢集配置檔案中讀取。

其他節點使用相同辦法啟動,不再贅述。需要特别注意,在啟動節點階段,節點是沒有主從關系的,是以從節點不需要加slaveof配置。

節點啟動以後是互相獨立的,并不知道其他節點存在;需要進行節點握手,将獨立的節點組成一個網絡。

節點握手使用cluster meet {ip} {port}指令實作,例如在7000節點中執行cluster meet 192.168.72.128 7001,可以完成7000節點和7001節點的握手;注意ip使用的是區域網路ip而不是localhost或127.0.0.1,是為了其他機器上的節點或用戶端也可以通路。此時再使用cluster nodes檢視:

深入學習Redis(5):叢集

在7001節點下也可以類似檢視:

深入學習Redis(5):叢集

同理,在7000節點中使用cluster meet指令,可以将所有節點加入到叢集,完成節點握手:

執行完上述指令後,可以看到7000節點已經感覺到了所有其他節點:

深入學習Redis(5):叢集

通過節點之間的通信,每個節點都可以感覺到所有其他節點,以8000節點為例:

深入學習Redis(5):叢集

在Redis叢集中,借助槽實作資料分區,具體原理後文會介紹。叢集有16384個槽,槽是資料管理和遷移的基本機關。當資料庫中的16384個槽都配置設定了節點時,叢集處于上線狀态(ok);如果有任意一個槽沒有配置設定節點,則叢集處于下線狀态(fail)。

cluster info指令可以檢視叢集狀态,配置設定槽之前狀态為fail:

深入學習Redis(5):叢集

配置設定槽使用cluster addslots指令,執行下面的指令将槽(編号0-16383)全部配置設定完畢:

此時檢視叢集狀态,顯示所有槽配置設定完畢,叢集進入上線狀态:

深入學習Redis(5):叢集

叢集中指定主從關系不再使用slaveof指令,而是使用cluster replicate指令;參數使用節點id。

通過cluster nodes獲得幾個主節點的節點id後,執行下面的指令為每個從節點指定主節點:

此時執行cluster nodes檢視各個節點的狀态,可以看到主從關系已經建立。

深入學習Redis(5):叢集

至此,叢集搭建完畢。

在{REDIS_HOME}/src目錄下可以看到redis-trib.rb檔案,這是一個Ruby腳本,可以實作自動化的叢集搭建。

(1)安裝Ruby環境

以Ubuntu為例,如下操作即可安裝Ruby環境:

(2)啟動節點

與第一種方法中的“啟動節點”完全相同。

(3)搭建叢集

redis-trib.rb腳本提供了衆多指令,其中create用于搭建叢集,使用方法如下:

其中:--replicas=1表示每個主節點有1個從節點;後面的多個{ip:port}表示節點位址,前面的做主節點,後面的做從節點。使用redis-trib.rb搭建叢集時,要求節點不能包含任何槽和資料。

執行建立指令後,腳本會給出建立叢集的計劃,如下圖所示;計劃包括哪些是主節點,哪些是從節點,以及如何配置設定槽。

深入學習Redis(5):叢集

輸入yes确認執行計劃,腳本便開始按照計劃執行,如下圖所示。

深入學習Redis(5):叢集

設計叢集方案時,至少要考慮以下因素:

(1)高可用要求:根據故障轉移的原理,至少需要3個主節點才能完成故障轉移,且3個主節點不應在同一台實體機上;每個主節點至少需要1個從節點,且主從節點不應在一台實體機上;是以高可用叢集至少包含6個節點。

(2)資料量和通路量:估算應用需要的資料量和總通路量(考慮業務發展,留有備援),結合每個主節點的容量和能承受的通路量(可以通過benchmark得到較準确估計),計算需要的主節點數量。

(3)節點數量限制:Redis官方給出的節點數量限制為1000,主要是考慮節點間通信帶來的消耗。在實際應用中應盡量避免大叢集;如果節點數量不足以滿足應用對Redis資料量和通路量的要求,可以考慮:(1)業務分割,大叢集分為多個小叢集;(2)減少不必要的資料;(3)調整資料過期政策等。

(4)适度備援:Redis可以在不影響叢集服務的情況下增加節點,是以節點數量适當備援即可,不用太大。

上一章介紹了叢集的搭建方法和設計方案,下面将進一步深入,介紹叢集的原理。叢集最核心的功能是資料分區,是以首先介紹資料的分區規則;然後介紹叢集實作的細節:通信機制和資料結構;最後以cluster meet(節點握手)、cluster addslots(槽配置設定)為例,說明節點是如何利用上述資料結構和通信機制實作叢集指令的。

資料分區有順序分區、哈希分區等,其中哈希分區由于其天然的随機性,使用廣泛;叢集的分區方案便是哈希分區的一種。

哈希分區的基本思路是:對資料的特征值(如key)進行哈希,然後根據哈希值決定資料落在哪個節點。常見的哈希分區包括:哈希取餘分區、一緻性哈希分區、帶虛拟節點的一緻性哈希分區等。

衡量資料分區方法好壞的标準有很多,其中比較重要的兩個因素是(1)資料分布是否均勻(2)增加或删減節點對資料分布的影響。由于哈希的随機性,哈希分區基本可以保證資料分布均勻;是以在比較哈希分區方案時,重點要看增減節點對資料分布的影響。

(1)哈希取餘分區

哈希取餘分區思路非常簡單:計算key的hash值,然後對節點數量進行取餘,進而決定資料映射到哪個節點上。該方案最大的問題是,當新增或删減節點時,節點數量發生變化,系統中所有的資料都需要重新計算映射關系,引發大規模資料遷移。

(2)一緻性哈希分區

一緻性雜湊演算法将整個哈希值空間組織成一個虛拟的圓環,如下圖所示,範圍為0-2^32-1;對于每個資料,根據key計算hash值,确定資料在環上的位置,然後從此位置沿環順時針行走,找到的第一台伺服器就是其應該映射到的伺服器。

深入學習Redis(5):叢集

圖檔來源:https://www.cnblogs.com/lpfuture/p/5796398.html

與哈希取餘分區相比,一緻性哈希分區将增減節點的影響限制在相鄰節點。以上圖為例,如果在node1和node2之間增加node5,則隻有node2中的一部分資料會遷移到node5;如果去掉node2,則原node2中的資料隻會遷移到node4中,隻有node4會受影響。

一緻性哈希分區的主要問題在于,當節點數量較少時,增加或删減節點,對單個節點的影響可能很大,造成資料的嚴重不平衡。還是以上圖為例,如果去掉node2,node4中的資料由總資料的1/4左右變為1/2左右,與其他節點相比負載過高。

(3)帶虛拟節點的一緻性哈希分區

該方案在一緻性哈希分區的基礎上,引入了虛拟節點的概念。Redis叢集使用的便是該方案,其中的虛拟節點稱為槽(slot)。槽是介于資料和實際節點之間的虛拟概念;每個實際節點包含一定數量的槽,每個槽包含哈希值在一定範圍内的資料。引入槽以後,資料的映射關系由資料hash->實際節點,變成了資料hash->槽->實際節點。

在使用了槽的一緻性哈希分區中,槽是資料管理和遷移的基本機關。槽解耦了資料和實際節點之間的關系,增加或删除節點對系統的影響很小。仍以上圖為例,系統中有4個實際節點,假設為其配置設定16個槽(0-15); 槽0-3位于node1,4-7位于node2,以此類推。如果此時删除node2,隻需要将槽4-7重新配置設定即可,例如槽4-5配置設定給node1,槽6配置設定給node3,槽7配置設定給node4;可以看出删除node2後,資料在其他節點的分布仍然較為均衡。

槽的數量一般遠小于2^32,遠大于實際節點的數量;在Redis叢集中,槽的數量為16384。

下面這張圖很好的總結了Redis叢集将資料映射到實際節點的過程:

深入學習Redis(5):叢集

圖檔修改自:https://blog.csdn.net/yejingtao703/article/details/78484151

(1)Redis對資料的特征值(一般是key)計算哈希值,使用的算法是CRC16。

(2)根據哈希值,計算資料屬于哪個槽。

(3)根據槽與節點的映射關系,計算資料屬于哪個節點。

叢集要作為一個整體工作,離不開節點之間的通信。

在哨兵系統中,節點分為資料節點和哨兵節點:前者存儲資料,後者實作額外的控制功能。在叢集中,沒有資料節點與非資料節點之分:所有的節點都存儲資料,也都參與叢集狀态的維護。為此,叢集中的每個節點,都提供了兩個TCP端口:

普通端口:即我們在前面指定的端口(7000等)。普通端口主要用于為用戶端提供服務(與單機節點類似);但在節點間資料遷移時也會使用。

叢集端口:端口号是普通端口+10000(10000是固定值,無法改變),如7000節點的叢集端口為17000。叢集端口隻用于節點之間的通信,如搭建叢集、增減節點、故障轉移等操作時節點間的通信;不要使用用戶端連接配接叢集接口。為了保證叢集可以正常工作,在配置防火牆時,要同時開啟普通端口和叢集端口。

節點間通信,按照通信協定可以分為幾種類型:單對單、廣播、Gossip協定等。重點是廣播和Gossip的對比。

廣播是指向叢集内所有節點發送消息;優點是叢集的收斂速度快(叢集收斂是指叢集内所有節點獲得的叢集資訊是一緻的),缺點是每條消息都要發送給所有節點,CPU、帶寬等消耗較大。

Gossip協定的特點是:在節點數量有限的網絡中,每個節點都“随機”的與部分節點通信(并不是真正的随機,而是根據特定的規則選擇通信的節點),經過一番雜亂無章的通信,每個節點的狀态很快會達到一緻。Gossip協定的優點有負載(比廣播)低、去中心化、容錯性高(因為通信有備援)等;缺點主要是叢集的收斂速度慢。

叢集中的節點采用固定頻率(每秒10次)的定時任務進行通信相關的工作:判斷是否需要發送消息及消息類型、确定接收節點、發送消息等。如果叢集狀态發生了變化,如增減節點、槽狀态變更,通過節點間的通信,所有節點會很快得知整個叢集的狀态,使叢集收斂。

節點間發送的消息主要分為5種:meet消息、ping消息、pong消息、fail消息、publish消息。不同的消息類型,通信協定、發送的頻率和時機、接收節點的選擇等是不同的。

MEET消息:在節點握手階段,當節點收到用戶端的CLUSTER MEET指令時,會向新加入的節點發送MEET消息,請求新節點加入到目前叢集;新節點收到MEET消息後會回複一個PONG消息。

PING消息:叢集裡每個節點每秒鐘會選擇部分節點發送PING消息,接收者收到消息後會回複一個PONG消息。PING消息的内容是自身節點和部分其他節點的狀态資訊;作用是彼此交換資訊,以及檢測節點是否線上。PING消息使用Gossip協定發送,接收節點的選擇兼顧了收斂速度和帶寬成本,具體規則如下:(1)随機找5個節點,在其中選擇最久沒有通信的1個節點(2)掃描節點清單,選擇最近一次收到PONG消息時間大于cluster_node_timeout/2的所有節點,防止這些節點長時間未更新。

PONG消息:PONG消息封裝了自身狀态資料。可以分為兩種:第一種是在接到MEET/PING消息後回複的PONG消息;第二種是指節點向叢集廣播PONG消息,這樣其他節點可以獲知該節點的最新資訊,例如故障恢複後新的主節點會廣播PONG消息。

FAIL消息:當一個主節點判斷另一個主節點進入FAIL狀态時,會向叢集廣播這一FAIL消息;接收節點會将這一FAIL消息儲存起來,便于後續的判斷。

PUBLISH消息:節點收到PUBLISH指令後,會先執行該指令,然後向叢集廣播這一消息,接收節點也會執行該PUBLISH指令。

節點需要專門的資料結構來存儲叢集的狀态。所謂叢集的狀态,是一個比較大的概念,包括:叢集是否處于上線狀态、叢集中有哪些節點、節點是否可達、節點的主從狀态、槽的分布……

節點為了存儲叢集狀态而提供的資料結構中,最關鍵的是clusterNode和clusterState結構:前者記錄了一個節點的狀态,後者記錄了叢集作為一個整體的狀态。

clusterNode結構儲存了一個節點的目前狀态,包括建立時間、節點id、ip和端口号等。每個節點都會用一個clusterNode結構記錄自己的狀态,并為叢集内所有其他節點都建立一個clusterNode結構來記錄節點狀态。

下面列舉了clusterNode的部分字段,并說明了字段的含義和作用:

除了上述字段,clusterNode還包含節點連接配接、主從複制、故障發現和轉移需要的資訊等。

clusterState結構儲存了在目前節點視角下,叢集所處的狀态。主要字段包括:

除此之外,clusterState還包括故障轉移、槽遷移等需要的資訊。

這一部分将以cluster meet(節點握手)、cluster addslots(槽配置設定)為例,說明節點是如何利用上述資料結構和通信機制實作叢集指令的。

假設要向A節點發送cluster meet指令,将B節點加入到A所在的叢集,則A節點收到指令後,執行的操作如下:

1)  A為B建立一個clusterNode結構,并将其添加到clusterState的nodes字典中

2)  A向B發送MEET消息

3)  B收到MEET消息後,會為A建立一個clusterNode結構,并将其添加到clusterState的nodes字典中

4)  B回複A一個PONG消息

5)  A收到B的PONG消息後,便知道B已經成功接收自己的MEET消息

6)  然後,A向B傳回一個PING消息

7)  B收到A的PING消息後,便知道A已經成功接收自己的PONG消息,握手完成

8)  之後,A通過Gossip協定将B的資訊廣播給叢集内其他節點,其他節點也會與B握手;一段時間後,叢集收斂,B成為叢集内的一個普通節點

通過上述過程可以發現,叢集中兩個節點的握手過程與TCP類似,都是三次握手:A向B發送MEET;B向A發送PONG;A向B發送PING。

叢集中槽的配置設定資訊,存儲在clusterNode的slots數組和clusterState的slots數組中,兩個數組的結構前面已做介紹;二者的差別在于:前者存儲的是該節點中配置設定了哪些槽,後者存儲的是叢集中所有槽分别分布在哪個節點。

cluster addslots指令接收一個槽或多個槽作為參數,例如在A節點上執行cluster addslots {0..10}指令,是将編号為0-10的槽配置設定給A節點,具體執行過程如下:

1)  周遊輸入槽,檢查它們是否都沒有配置設定,如果有一個槽已配置設定,指令執行失敗;方法是檢查輸入槽在clusterState.slots[]中對應的值是否為NULL。

2)  周遊輸入槽,将其配置設定給節點A;方法是修改clusterNode.slots[]中對應的比特為1,以及clusterState.slots[]中對應的指針指向A節點

3)  A節點執行完成後,通過節點通信機制通知其他節點,所有節點都會知道0-10的槽配置設定給了A節點

在叢集中,資料分布在不同的節點中,用戶端通過某節點通路資料時,資料可能不在該節點中;下面介紹叢集是如何處理這個問題的。

當節點收到redis-cli發來的指令(如set/get)時,過程如下:

(1)計算key屬于哪個槽:CRC16(key) & 16383

叢集提供的cluster keyslot指令也是使用上述公式實作,如:

深入學習Redis(5):叢集

(2)判斷key所在的槽是否在目前節點:假設key位于第i個槽,clusterState.slots[i]則指向了槽所在的節點,如果clusterState.slots[i]==clusterState.myself,說明槽在目前節點,可以直接在目前節點執行指令;否則,說明槽不在目前節點,則查詢槽所在節點的位址(clusterState.slots[i].ip/port),并将其包裝到MOVED錯誤中傳回給redis-cli。

(3)redis-cli收到MOVED錯誤後,根據傳回的ip和port重新發送請求。

下面的例子展示了redis-cli和叢集的互動過程:在7000節點中操作key1,但key1所在的槽9189在節點7001中,是以節點傳回MOVED錯誤(包含7001節點的ip和port)給redis-cli,redis-cli重新向7001發起請求。

深入學習Redis(5):叢集

上例中,redis-cli通過-c指定了叢集模式,如果沒有指定,redis-cli無法處理MOVED錯誤:

深入學習Redis(5):叢集

redis-cli這一類用戶端稱為Dummy用戶端,因為它們在執行指令前不知道資料在哪個節點,需要借助MOVED錯誤重新定向。與Dummy用戶端相對應的是Smart用戶端。

Smart用戶端(以Java的JedisCluster為例)的基本原理:

(1)JedisCluster初始化時,在内部維護slot->node的緩存,方法是連接配接任一節點,執行cluster slots指令,該指令傳回如下所示:

深入學習Redis(5):叢集

(2)此外,JedisCluster為每個節點建立連接配接池(即JedisPool)。

(3)當執行指令時,JedisCluster根據key->slot->node選擇需要連接配接的節點,發送指令。如果成功,則指令執行完畢。如果執行失敗,則會随機選擇其他節點進行重試,并在出現MOVED錯誤時,使用cluster slots重新同步slot->node的映射關系。

下面代碼示範了如何使用JedisCluster通路叢集(未考慮資源釋放、異常處理等):

注意事項如下:

(1)JedisCluster中已經包含所有節點的連接配接池,是以JedisCluster要使用單例。

(2)用戶端維護了slot->node映射關系以及為每個節點建立了連接配接池,當節點數量較多時,應注意用戶端記憶體資源和連接配接資源的消耗。

(3)Jedis較新版本針對JedisCluster做了一些性能方面的優化,如cluster slots緩存更新和鎖阻塞等方面的優化,應盡量使用2.8.2及以上版本的Jedis。

前面介紹了叢集正常運作和通路的方法和原理,下面是一些重要的補充内容。

實踐中常常需要對叢集進行伸縮,如通路量增大時的擴容操作。Redis叢集可以在不影響對外服務的情況下實作伸縮;伸縮的核心是槽遷移:修改槽與節點的對應關系,實作槽(即資料)在節點之間的移動。例如,如果槽均勻分布在叢集的3個節點中,此時增加一個節點,則需要從3個節點中分别拿出一部分槽給新節點,進而實作槽在4個節點中的均勻分布。

假設要增加7003和8003節點,其中8003是7003的從節點;步驟如下:

(1)啟動節點:方法參見叢集搭建

(2)節點握手:可以使用cluster meet指令,但在生産環境中建議使用redis-trib.rb的add-node工具,其原理也是cluster meet,但它會先檢查新節點是否已加入其它叢集或者存在資料,避免加入到叢集後帶來混亂。

(3)遷移槽:推薦使用redis-trib.rb的reshard工具實作。reshard自動化程度很高,隻需要輸入redis-trib.rb reshard ip:port (ip和port可以是叢集中的任一節點),然後按照提示輸入以下資訊,槽遷移會自動完成:

待遷移的槽數量:16384個槽均分給4個節點,每個節點4096個槽,是以待遷移槽數量為4096

目标節點id:7003節點的id

源節點的id:7000/7001/7002節點的id

(4)指定主從關系:方法參見叢集搭建

假設要下線7000/8000節點,可以分為兩步:

(1)遷移槽:使用reshard将7000節點中的槽均勻遷移到7001/7002/7003節點

(2)下線節點:使用redis-trib.rb del-node工具;應先下線從節點再下線主節點,因為若主節點先下線,從節點會被指向其他主節點,造成不必要的全量複制。

叢集伸縮的核心是槽遷移。在槽遷移過程中,如果用戶端向源節點發送指令,源節點執行流程如下:

深入學習Redis(5):叢集

圖檔來源:《Redis設計與實作》

用戶端收到ASK錯誤後,從中讀取目标節點的位址資訊,并向目标節點重新發送請求,就像收到MOVED錯誤時一樣。但是二者有很大差別:ASK錯誤說明資料正在遷移,不知道何時遷移完成,是以重定向是臨時的,SMART用戶端不會重新整理slots緩存;MOVED錯誤重定向則是(相對)永久的,SMART用戶端會重新整理slots緩存。

在 哨兵 一文中,介紹了哨兵實作故障發現和故障轉移的原理。雖然細節上有很大不同,但叢集的實作與哨兵思路類似:通過定時任務發送PING消息檢測其他節點狀态;節點下線分為主觀下線和客觀下線;客觀下線後選取從節點進行故障轉移。

與哨兵一樣,叢集隻實作了主節點的故障轉移;從節點故障時隻會被下線,不會進行故障轉移。是以,使用叢集時,應謹慎使用讀寫分離技術,因為從節點故障會導緻讀服務不可用,可用性變差。

這裡不再詳細介紹故障轉移的細節,隻對重要事項進行說明:

節點數量:在故障轉移階段,需要由主節點投票選出哪個從節點成為新的主節點;從節點選舉勝出需要的票數為N/2+1;其中N為主節點數量(包括故障主節點),但故障主節點實際上不能投票。是以為了能夠在故障發生時順利選出從節點,叢集中至少需要3個主節點(且部署在不同的實體機上)。

故障轉移時間:從主節點故障發生到完成轉移,所需要的時間主要消耗在主觀下線識别、主觀下線傳播、選舉延遲等幾個環節;具體時間與參數cluster-node-timeout有關,一般來說:

故障轉移時間(毫秒) ≤ 1.5 * cluster-node-timeout + 1000

cluster-node-timeout的預設值為15000ms(15s),是以故障轉移時間會在20s量級。

由于叢集中的資料分布在不同節點中,導緻一些功能受限,包括:

(1)key批量操作受限:例如mget、mset操作,隻有當操作的key都位于一個槽時,才能進行。針對該問題,一種思路是在用戶端記錄槽與key的資訊,每次針對特定槽執行mget/mset;另外一種思路是使用Hash Tag,将在下一小節介紹。

(2)keys/flushall等操作:keys/flushall等操作可以在任一節點執行,但是結果隻針對目前節點,例如keys操作隻傳回目前節點的所有鍵。針對該問題,可以在用戶端使用cluster nodes擷取所有節點資訊,并對其中的所有主節點執行keys/flushall等操作。

(3)事務/Lua腳本:叢集支援事務及Lua腳本,但前提條件是所涉及的key必須在同一個節點。Hash Tag可以解決該問題。

(4)資料庫:單機Redis節點可以支援16個資料庫,叢集模式下隻支援一個,即db0。

(5)複制結構:隻支援一層複制結構,不支援嵌套。

Hash Tag原理是:當一個key包含 {} 的時候,不對整個key做hash,而僅對 {} 包括的字元串做hash。

Hash Tag可以讓不同的key擁有相同的hash值,進而配置設定在同一個槽裡;這樣針對不同key的批量操作(mget/mset等),以及事務、Lua腳本等都可以支援。不過Hash Tag可能會帶來資料配置設定不均的問題,這時需要:(1)調整不同節點中槽的數量,使資料分布盡量均勻;(2)避免對熱點資料使用Hash Tag,導緻請求分布不均。

下面是使用Hash Tag的一個例子;通過對product加Hash Tag,可以将所有産品資訊放到同一個槽中,便于操作。

深入學習Redis(5):叢集

cluster_node_timeout參數在前面已經初步介紹;它的預設值是15s,影響包括:

(1)影響PING消息接收節點的選擇:值越大對延遲容忍度越高,選擇的接收節點越少,可以降低帶寬,但會降低收斂速度;應根據帶寬情況和應用要求進行調整。

(2)影響故障轉移的判定和時間:值越大,越不容易誤判,但完成轉移消耗時間越長;應根據網絡狀況和應用要求進行調整。

前面提到,隻有當16384個槽全部配置設定完畢時,叢集才能上線。這樣做是為了保證叢集的完整性,但同時也帶來了新的問題:當主節點發生故障而故障轉移尚未完成,原主節點中的槽不在任何節點中,此時會叢集處于下線狀态,無法響應用戶端的請求。

cluster-require-full-coverage參數可以改變這一設定:如果設定為no,則當槽沒有完全配置設定時,叢集仍可以上線。參數預設值為yes,如果應用對可用性要求較高,可以修改為no,但需要自己保證槽全部配置設定。

redis-trib.rb提供了衆多實用工具:建立叢集、增減節點、槽遷移、檢查完整性、資料重新平衡等;通過help指令可以檢視詳細資訊。在實踐中如果能使用redis-trib.rb工具則盡量使用,不但友善快捷,還可以大大降低出錯機率。

《Redis開發與運維》

《Redis設計與實作》

https://redis.io/topics/cluster-tutorial

https://redis.io/topics/cluster-spec

https://mp.weixin.qq.com/s/d6hzmk31o7VBsMYaLdQ5mw

https://www.cnblogs.com/lpfuture/p/5796398.html

http://www.zsythink.net/archives/1182/

https://www.cnblogs.com/xxdfly/p/5641719.html

創作不易,如果文章對你有幫助,就點個贊、評個論呗~

繼續閱讀