redis叢集高可用 (圖解+秒懂+史上最全)
高可用Redis(十二):Redis Cluster
Redis高可用常見的有兩種方式:
Redis高可用常見的有兩種方式:
- 主從複製(Replication-Sentinel模式)
- Redis叢集(Redis-Cluster模式)
下面將分別介紹這兩種高可用方案。
搭建環境:
redis版本:redis-5.0.4
伺服器環境:centos7
Redis叢集(Redis-Cluster)
Redis 叢集是一個提供在多個Redis節點間共享資料的程式集。
下圖以三個master節點和三個slave節點作為示例。
Redis 叢集有16384個雜湊槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽。
叢集的每個節點負責一部分hash槽,如圖中slots所示。
為了使在部分節點失敗或者大部分節點無法通訊的情況下叢集仍然可用,所以叢集使用了主從複製模型,每個節點都會有1-n個從節點。
例如master-A節點不可用了,叢集便會選舉slave-A節點作為新的主節點繼續服務。
資料分片(sharding)的基本原理
什麼是資料分片?
名詞說明:
資料分片(sharding)也叫資料分割槽
為什麼要做資料分片?
全量資料較大的場景下,單節點無法滿足要求,需要資料分片
什麼是資料分片?
按照分片規則把資料分到若干個子集當中
range 分片
一種是按照 range 來分,就是每個片,一段連續的資料,這個一般是按比如時間範圍
比如:1到100個數字,要儲存在3個節點上
按照順序分片,把資料平均分配三個節點上
- 1號到33號資料儲存到節點1上
- 34號到66號資料儲存到節點2上
- 67號到100號資料儲存到節點3上
ID取模分片
此種分片規則將資料分成n份(通常dn節點也為n),從而將資料均勻的分佈於各個表中,或者各節點上。
擴容方便。
ID取模分片常用在關係型資料庫的設計
具體請參見 秒殺視訊的 億級庫表架構設計
hash 雜湊分佈
例如1到100個數字,對每個數字進行雜湊運算,然後對每個數的雜湊結果除以節點數進行取餘,餘數為1則儲存在第1個節點上,餘數為2則儲存在第2個節點上,餘數為0則儲存在第3個節點,這樣可以保證資料被打散,同時保證資料分佈的比較均勻
雜湊分佈方式分為三個分片方式:
- 雜湊取餘分片
- 一致性雜湊分片
- 虛擬槽分片
雜湊取餘分片
比如有100個數據,對每個資料進行hash運算之後,與節點數進行取餘運算,根據餘數不同儲存在不同的節點上
雜湊取餘分片是非常簡單的一種分片方式
雜湊取餘分片有一個問題
即當增加或減少節點時,原來節點中的80%的資料會進行遷移操作,對所有資料重新進行分佈
雜湊取餘分片,建議使用多倍擴容的方式,例如以前用3個節點儲存資料,擴容為比以前多一倍的節點即6個節點來儲存資料,這樣只需要適移50%的資料。
資料遷移之後,第一次無法從快取中讀取資料,必須先從資料庫中讀取資料,然後回寫到快取中,然後才能從快取中讀取遷移之後的資料
雜湊取餘分片優點:
- 配置簡單:對資料進行雜湊,然後取餘
雜湊取餘分片缺點:
- 資料節點伸縮時,導致資料遷移
- 遷移數量和新增節點資料有關,建議翻倍擴容
一致性雜湊分片
一致性雜湊原理:
將所有的資料當做一個token環,
token環中的資料範圍是0到2的32次方。
然後為每一個數據節點分配一個token範圍值,這個節點就負責儲存這個範圍內的資料。
對每一個key進行hash運算,被雜湊後的結果在哪個token的範圍內,則按順時針去找最近的節點,這個key將會被儲存在這個節點上。
一致性雜湊分片的節點擴容
在下面的圖中:
-
有4個key被hash之後的值在在n1節點和n2節點之間,按照順時針規則,這4個key都會被儲存在n2節點上
-
如果在n1節點和n2節點之間新增n5節點,當下次有key被hash之後的值在n1節點和n5節點之間,這些key就會被儲存在n5節點上面了
下圖的例子裡,新增n5節點之後:
- 資料遷移會在n1節點和n2節點之間進行
- n3節點和n4節點不受影響
- 資料遷移範圍被縮小很多
同理,如果有1000個節點,此時新增一個節點,受影響的節點範圍最多隻有千分之2。所以,一致性雜湊一般用在節點比較多的時候,節點越多,擴容時受影響的節點範圍越少
分片方式:雜湊 + 順時針(優化取餘)
一致性雜湊分片優點:
- 一致性雜湊演算法解決了分散式下資料分佈問題。比如在快取系統中,通過一致性雜湊演算法把快取鍵對映到不同的節點上,由於演算法中虛擬節點的存在,雜湊結果一般情況下比較均勻。
- 節點伸縮時,隻影響鄰近節點,但是還是有資料遷移
“但沒有一種解決方案是銀彈,能適用於任何場景。所以實踐中一致性雜湊演算法有哪些缺陷,或者有哪些場景不適用呢?”
一致性雜湊分片缺點:
一致性雜湊在大批量的資料場景下負載更加均衡,但是在資料規模小的場景下,會出現單位時間內某個節點完全空閒的情況出現。
虛擬槽分片
Redis Cluster在設計中沒有使用一致性雜湊(Consistency Hashing),而是使用資料分片引入雜湊槽(hash slot)來實現;
虛擬槽分片是Redis Cluster採用的分片方式.
在該分片方式中:
- 首先 預設虛擬槽,每個槽就相當於一個數字,有一定範圍。
- 每個槽對映一個數據子集,一般比節點數大
Redis Cluster中預設虛擬槽的範圍為0到16383
虛擬槽分片的對映步驟:
1.把16384槽按照節點數量進行平均分配,由節點進行管理
2.對每個key按照CRC16規則進行hash運算
3.把hash結果對16383進行取餘
4.把餘數傳送給Redis節點
5.節點接收到資料,驗證是否在自己管理的槽編號的範圍
- 如果在自己管理的槽編號範圍內,則把資料儲存到資料槽中,然後返回執行結果
- 如果在自己管理的槽編號範圍外,則會把資料傳送給正確的節點,由正確的節點來把資料儲存在對應的槽中
需要注意的是:Redis Cluster的節點之間會共享訊息,每個節點都會知道是哪個節點負責哪個範圍內的資料槽
虛擬槽分佈方式中,由於每個節點管理一部分資料槽,資料儲存到資料槽中。
當節點擴容或者縮容時,對資料槽進行重新分配遷移即可,資料不會丟失。
虛擬槽分片特點:
虛擬槽分割槽巧妙地使用了雜湊空間,使用分散度良好的雜湊函式把所有資料對映到一個固定範圍的整數集合中,整數定義為槽(slot)。槽是叢集內資料管理和遷移的基本單位。
槽的範圍一般遠遠大於節點數,比如Redis Cluster槽範圍是0~16383。
採用大範圍槽的主要目的是為了方便資料拆分和叢集擴充套件,每個節點會負責一定數量的槽。
Redis虛擬槽分割槽的優點:
-
解耦資料和節點之間的關係,簡化了節點擴容和收縮難度。
-
節點自身維護槽的對映關係,不需要客戶端或者代理服務維護槽分割槽元資料。
-
支援節點、槽、鍵之間的對映查詢,用於資料路由,線上伸縮等場景。
-
無論資料規模大,還是小,Redis虛擬槽分割槽各個節點的負載,都會比較均衡 。而一致性雜湊在大批量的資料場景下負載更加均衡,但是在資料規模小的場景下,會出現單位時間內某個節點完全空閒的情況出現。
Redis叢集如何高可用
要實現Redis高可用,前提條件之一,是需要進行Redis的節點叢集
叢集的必要性
所謂的叢集,就是通過新增服務節點的數量,不同的節點提供相同的服務,從而讓伺服器達到高可用、自動failover的狀態。
面試題:單個redis節點,面臨哪些問題?
答:
(1)單個redis存在不穩定性。當redis服務宕機了,就沒有可用的服務了。
(2)單個redis的讀寫能力是有限的。單機的 redis,能夠承載的 QPS 大概就在上萬到幾萬不等。
對於快取來說,一般都是用來支撐讀高併發、高可用。單個redis節點,二者都做不到。
Redis叢集模式的分類,可以從下面角度來分:
- 客戶端分片
- 代理分片
- 服務端分片
- 代理模式和服務端分片相結合的模式
客戶端分片包括:
ShardedJedisPool
ShardedJedisPool是redis沒有叢集功能之前客戶端實現的一個數據分散式方案,
使用shardedJedisPool實現redis叢集部署,由於shardedJedisPool的原理是通過一致性雜湊進行切片實現的,不同點key被分別分配到不同的redis例項上。
代理分片包括:
- Codis
- Twemproxy
服務端分片包括:
- Redis Cluster
從否中心化來劃分
它們還可以用是否中心化來劃分
- 無中心化的叢集方案
其中客戶端分片、Redis Cluster屬於無中心化的叢集方案
- 中心化的叢集方案
Codis、Tweproxy屬於中心化的叢集方案。
是否中心化是指客戶端訪問多個Redis節點時,是直接訪問還是通過一箇中間層Proxy來進行操作,直接訪問的就屬於無中心化的方案,通過中間層Proxy訪問的就屬於中心化的方案,它們有各自的優劣,下面分別來介紹。
如何學習redis叢集
說明:
(1)redis叢集中,每一個redis稱之為一個節點。
(2)redis叢集中,有兩種型別的節點:主節點(master)、從節點(slave)。
(3)redis叢集,是基於redis主從複製實現。
所以,學習redis叢集,就是從學習redis主從模式開始的。
而學習主從模式,需要從redis主從複製開始。
redis主從複製
主從複製,是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。前者稱為主節點(master),後者稱為從節點(slave);資料的複製是單向的,只能由主節點到從節點。
預設情況下,每臺Redis伺服器都是主節點;且一個主節點可以有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。
主從複製的作用
主從複製的作用主要包括:
- 資料冗餘:主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘方式。
- 故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗餘。
- 負載均衡:在主從複製的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連線主節點,讀Redis資料時應用連線從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的併發量。
- 高可用基石:除了上述作用以外,主從複製還是哨兵和叢集能夠實施的基礎,因此說主從複製是Redis高可用的基礎。
開啟主從複製的方式
需要注意,主從複製的開啟,完全是在從節點發起的;不需要我們在主節點做任何事情。
從節點開啟主從複製,有3種方式:
(1)配置檔案
在從伺服器的配置檔案中加入:slaveof
(2)啟動命令
redis-server啟動命令後加入 --slaveof
(3)客戶端命令
Redis伺服器啟動後,直接通過客戶端執行命令:slaveof ,則該Redis例項成為從節點。
上述3種方式是等效的,下面以客戶端命令的方式為例,看一下當執行了slaveof後,Redis主節點和從節點的變化。
主從複製例項
準備工作:啟動兩個節點
實驗所使用的主從節點是在一臺機器上的不同Redis例項,其中:
- 主節點監聽6379埠,
- 從節點監聽6380埠;
- 從節點監聽的埠號可以在配置檔案中修改:
啟動後可以看到:
兩個Redis節點啟動後(分別稱為6379節點和6380節點),預設都是主節點。
建立複製關係
此時在6380節點執行slaveof命令,使之變為從節點:
觀察效果
下面驗證一下,在主從複製建立後,主節點的資料會複製到從節點中。
(1)首先在從節點查詢一個不存在的key:
(2)然後在主節點中增加這個key:
(3)此時在從節點中再次查詢這個key,會發現主節點的操作已經同步至從節點:
(4)然後在主節點刪除這個key:
(5)此時在從節點中再次查詢這個key,會發現主節點的操作已經同步至從節點:
斷開復制
通過slaveof 命令建立主從複製關係以後,可以通過slaveof no one斷開。需要注意的是,從節點斷開復制後,不會刪除已有的資料,只是不再接受主節點新的資料變化。
從節點執行slaveof no one後,列印日誌如下所示;
可以看出斷開復制後,從節點又變回為主節點。
斷開復制後,主節點列印日誌如下:
主從複製的核心原理
1 當啟動一個 slave node 的時候,它會發送一個 PSYNC 命令給 master node。
2 如果這是 slave node 初次連線到 master node,那麼會觸發一次 full resynchronization 全量複製。
master node 怎麼進行 full resynchronization 全量複製?
此時 master 會啟動一個後臺執行緒,開始生成一份 RDB 快照檔案,同時還會將從客戶端 client 新收到的所有寫命令快取在記憶體中。
RDB 檔案生成完畢後, master 會將這個 RDB 傳送給 slave,
slave node 接收到RDB ,幹啥呢?
會先寫入本地磁碟,然後再從本地磁碟載入到記憶體中,
3 資料同步階段完成後,主從節點進入命令傳播階段;在這個階段master 將自己執行的寫命令傳送給從節點,從節點接收命令並執行,從而保證主從節點資料的一致性。
4 部分複製。如果slave node跟 master node 有網路故障,斷開了連線,會自動重連,連線之後 master node 僅會複製給 slave 部分缺少的資料。
主從複製的核心流程
主從複製過程大體可以分為3個階段:
- 連線建立階段(即準備階段)
- 資料同步階段
- 命令傳播階段;
下面分別進行介紹。
連線建立階段
該階段的主要作用是在主從節點之間建立連線,為資料同步做好準備。
步驟1:儲存主節點資訊
從節點伺服器內部維護了兩個欄位,即masterhost和masterport欄位,用於儲存主節點的ip和port資訊。
需要注意的是,slaveof是非同步命令,從節點完成主節點ip和port的儲存後,向傳送slaveof命令的客戶端直接返回OK,實際的複製操作在這之後才開始進行。
這個過程中,可以看到從節點列印日誌如下:
步驟2:建立socket連線
slave 從節點每秒1次呼叫複製定時函式replicationCron(),如果發現了有主節點可以連線,便會根據主節點的ip和port,建立socket連線。
如果連線成功,則:
- 從節點:
為該socket建立一個專門處理複製工作的檔案事件處理器,負責後續的複製工作,如接收RDB檔案、接收命令傳播等。
- 主節點:
接收到從節點的socket連線後(即accept之後),為該socket建立相應的客戶端狀態,並將從節點看做是連線到主節點的一個客戶端,後面的步驟會以從節點向主節點發送命令請求的形式來進行。
這個過程中,從節點列印日誌如下:
步驟3:傳送ping命令
從節點成為主節點的客戶端之後,傳送ping命令進行首次請求,目的是:檢查socket連線是否可用,以及主節點當前是否能夠處理請求。
從節點發送ping命令後,可能出現3種情況:
(1)返回pong:說明socket連線正常,且主節點當前可以處理請求,複製過程繼續。
(2)超時:一定時間後從節點仍未收到主節點的回覆,說明socket連線不可用,則從節點斷開socket連線,並重連。
(3)返回pong以外的結果:如果主節點返回其他結果,如正在處理超時執行的指令碼,說明主節點當前無法處理命令,則從節點斷開socket連線,並重連。
在主節點返回pong情況下,從節點列印日誌如下:
步驟4:身份驗證
如果從節點中設定了masterauth選項,則從節點需要向主節點進行身份驗證;沒有設定該選項,則不需要驗證。從節點進行身份驗證是通過向主節點發送auth命令進行的,auth命令的引數即為配置檔案中的master auth的值。
則身份驗證通過,複製過程繼續;如果不一致,則從節點斷開socket連線,並重連。
步驟5:傳送從節點埠資訊
身份驗證之後,從節點會向主節點發送其監聽的埠號(前述例子中為6380),主節點將該資訊儲存到該從節點對應的客戶端的slave_listening_port欄位中;該埠資訊除了在主節點中執行info Replication時顯示以外,沒有其他作用。
資料同步階段
主從節點之間的連線建立以後,便可以開始進行資料同步,該階段可以理解為從節點資料的初始化。
具體執行的方式是:從節點向主節點發送psync命令(Redis2.8以前是sync命令),開始同步。
資料同步階段是主從複製最核心的階段,根據主從節點當前狀態的不同,可以分為全量複製和部分複製。
在Redis2.8以前,從節點向主節點發送sync命令請求同步資料,此時的同步方式是全量複製;
在Redis2.8及以後,從節點可以傳送psync命令請求同步資料,此時根據主從節點當前狀態的不同,同步方式可能是全量複製或部分複製。後文介紹以Redis2.8及以後版本為例。
- 全量複製:用於初次複製或其他無法進行部分複製的情況,將主節點中的所有資料都發送給從節點,是一個非常重型的操作。
- 部分複製:用於網路中斷等情況後的複製,只將中斷期間主節點執行的寫命令傳送給從節點,與全量複製相比更加高效。需要注意的是,如果網路中斷時間過長,導致主節點沒有能夠完整地儲存中斷期間執行的寫命令,則無法進行部分複製,仍使用全量複製。
全量複製
Redis通過psync命令進行全量複製的過程如下:
(1)從節點判斷無法進行部分複製,向主節點發送全量複製的請求;或從節點發送部分複製的請求,但主節點判斷無法進行部分複製;
(2)主節點收到全量複製的命令後,執行bgsave,在後臺生成RDB檔案,並使用一個緩衝區(稱為複製緩衝區)記錄從現在開始執行的所有寫命令
(3)主節點的bgsave執行完成後,將RDB檔案傳送給從節點;從節點首先清除自己的舊資料,然後載入接收的RDB檔案,將資料庫狀態更新至主節點執行bgsave時的資料庫狀態
(4)主節點將前述複製緩衝區中的所有寫命令傳送給從節點,從節點執行這些寫命令,將資料庫狀態更新至主節點的最新狀態
(5)如果從節點開啟了AOF,則會觸發bgrewriteaof的執行,從而保證AOF檔案更新至主節點的最新狀態
下面是執行全量複製時,主從節點列印的日誌;可以看出日誌內容與上述步驟是完全對應的。
主節點的列印日誌如下:
從節點列印日誌如下圖所示:
其中,有幾點需要注意:從節點接收了來自主節點的89260個位元組的資料;從節點在載入主節點的資料之前要先將老資料清除;從節點在同步完資料後,呼叫了bgrewriteaof。
通過全量複製的過程可以看出,全量複製是非常重型的操作:
(1)效能損耗:主節點通過bgsave命令fork子程序進行RDB持久化,該過程是非常消耗CPU、記憶體(頁表複製)、硬碟IO的;
(2)頻寬佔用:主節點通過網路將RDB檔案傳送給從節點,對主從節點的頻寬都會帶來很大的消耗
(3)停車載入:從節點清空老資料、載入新RDB檔案的過程是阻塞的,無法響應客戶端的命令;如果從節點執行bgrewriteaof,也會帶來額外的消耗
題外話:什麼是Redis Bgrewriteaof ?
Redis Bgrewriteaof 命令用於非同步執行一個 AOF(AppendOnly File) 檔案重寫操作。重寫會建立一個當前 AOF 檔案的體積優化版本。
即使 Bgrewriteaof 執行失敗,也不會有任何資料丟失,因為舊的 AOF 檔案在 Bgrewriteaof 成功之前不會被修改。
注意:從 Redis 2.4 開始, AOF 重寫由 Redis 自行觸發, BGREWRITEAOF 僅僅用於手動觸發重寫操作。
redis Bgrewriteaof 命令基本語法如下:
redis 127.0.0.1:6379> BGREWRITEAOF
redis2.8 版本之前主從複製流程
redis2.8 版本之前主從複製流程:
- 從伺服器連線主伺服器,傳送SYNC命令;
- 主伺服器接收到SYNC命名後,開始執行BGSAVE命令生成RDB檔案並使用緩衝區記錄此後執行的所有寫命令;
- 主伺服器BGSAVE執行完後,向所有從伺服器傳送快照檔案,並在傳送期間繼續記錄被執行的寫命令;
- 從伺服器收到快照檔案後丟棄所有舊資料,載入收到的快照;
- 主伺服器快照發送完畢後開始向從伺服器傳送緩衝區中的寫命令;
- 從伺服器完成對快照的載入,開始接收命令請求,並執行來自主伺服器緩衝區的寫命令;
全量複製的弊端:
場景:(1)新建立的slave,從主機master同步資料。(2)剛宕機一小會的slave,從主機master同步資料。
前者新建的slave則從主機master全量同步資料,這沒啥問題。但是後者slave可能只與主機master存在小量的資料差異,要是全量同步肯定沒有隻同步差異(部分複製)的那點資料效能高
部分複製
由於全量複製在主節點資料量較大時效率太低,因此Redis2.8開始提供部分複製,用於處理網路中斷時的資料同步。
部分複製的實現,依賴於三個重要的概念:
(1)複製偏移量
主節點和從節點分別維護一個複製偏移量(offset),代表的是主節點向從節點傳遞的位元組數;主節點每次向從節點傳播N個位元組資料時,主節點的offset增加N;從節點每次收到主節點傳來的N個位元組資料時,從節點的offset增加N。
offset用於判斷主從節點的資料庫狀態是否一致:如果二者offset相同,則一致;如果offset不同,則不一致,此時可以根據兩個offset找出從節點缺少的那部分資料。
例如,如果主節點的offset是1000,而從節點的offset是500,那麼部分複製就需要將offset為501-1000的資料傳遞給從節點。而offset為501-1000的資料儲存的位置,就是下面要介紹的複製積壓緩衝區。
(2)複製積壓緩衝區
複製積壓緩衝區是由主節點維護的、固定長度的、先進先出(FIFO)佇列,預設大小1MB;
當主節點開始有從節點時, master建立一個複製積壓緩衝區,其作用是備份主節點最近傳送給從節點的資料。
注意,無論主節點有一個還是多個從節點,都只需要一個複製積壓緩衝區。
在命令傳播階段,主節點除了將寫命令傳送給從節點,還會發送一份給複製積壓緩衝區,作為寫命令的備份;
除了儲存寫命令,複製積壓緩衝區中還儲存了其中的每個位元組對應的複製偏移量(offset)。
由於複製積壓緩衝區定長且是先進先出,所以它儲存的是主節點最近執行的寫命令;時間較早的寫命令會被擠出緩衝區。
由於該緩衝區長度固定且有限,因此可以備份的寫命令也有限,當主從節點offset的差距過大超過緩衝區長度時,將無法執行部分複製,只能執行全量複製。
反過來說,為了提高網路中斷時部分複製執行的概率,可以根據需要增大複製積壓緩衝區的大小(通過配置repl-backlog-size);例如如果網路中斷的平均時間是60s,而主節點平均每秒產生的寫命令(特定協議格式)所佔的位元組數為100KB,則複製積壓緩衝區的平均需求為6MB,保險起見,可以設定為12MB,來保證絕大多數斷線情況都可以使用部分複製。
從節點將offset傳送給主節點後,主節點根據offset和緩衝區大小決定能否執行部分複製:
- 如果offset偏移量之後的資料,仍然都在複製積壓緩衝區裡,則執行部分複製;
- 如果offset偏移量之後的資料已不在複製積壓緩衝區中(資料已被擠出),則執行全量複製。
(3)伺服器執行ID(runid)
每個Redis節點(無論主從),在啟動時都會自動生成一個隨機ID(每次啟動都不一樣),由40個隨機的十六進位制字元組成;runid用來唯一識別一個Redis節點。
通過info Server命令,可以檢視節點的runid:
主從節點初次複製時,主節點將自己的runid傳送給從節點,從節點將這個runid儲存起來;當斷線重連時,從節點會將這個runid傳送給主節點;主節點根據runid判斷能否進行部分複製:
- 如果從節點儲存的runid與主節點現在的runid相同,說明主從節點之前同步過,主節點會繼續嘗試使用部分複製(到底能不能部分複製還要看offset和複製積壓緩衝區的情況);
- 如果從節點儲存的runid與主節點現在的runid不同,說明從節點在斷線前同步的Redis節點並不是當前的主節點,只能進行全量複製。
psync命令的執行
在瞭解了複製偏移量、複製積壓緩衝區、節點執行id之後,本節將介紹psync命令的引數和返回值,從而說明psync命令執行過程中,主從節點是如何確定使用全量複製還是部分複製的。
psync命令的執行過程可以參見下圖:
(1)首先,從節點根據當前狀態,決定如何呼叫psync命令:
- 如果從節點之前未執行過slaveof或最近執行了slaveof no one,則從節點發送命令為psync ? -1,向主節點請求全量複製;
- 如果從節點之前執行了slaveof,則傳送命令為psync
,其中runid為上次複製的主節點的runid,offset為上次複製截止時從節點儲存的複製偏移量。
(2)主節點根據收到的psync命令,及當前伺服器狀態,決定執行全量複製還是部分複製:
- 如果主節點版本低於Redis2.8,則返回-ERR回覆,此時從節點重新發送sync命令執行全量複製;
- 如果主節點版本夠新,且runid與從節點發送的runid相同,且從節點發送的offset之後的資料在複製積壓緩衝區中都存在,則回覆+CONTINUE,表示將進行部分複製,從節點等待主節點發送其缺少的資料即可;
- 如果主節點版本夠新,但是runid與從節點發送的runid不同,或從節點發送的offset之後的資料已不在複製積壓緩衝區中(在佇列中被擠出了),則回覆+FULLRESYNC
,表示要進行全量複製,其中runid表示主節點當前的runid,offset表示主節點當前的offset,從節點儲存這兩個值,以備使用。
命令傳播階段
資料同步階段完成後,主從節點進入命令傳播階段;在這個階段主節點將自己執行的寫命令傳送給從節點,從節點接收命令並執行,從而保證主從節點資料的一致性。
在命令傳播階段,除了傳送寫命令,主從節點還維持著心跳機制:PING和REPLCONF ACK。
心跳機制對於主從複製的超時判斷、資料安全等有作用。
1.主->從:PING
每隔指定的時間,主節點會向從節點發送PING命令,這個PING命令的作用,主要是為了讓從節點進行超時判斷。
PING傳送的頻率由repl-ping-slave-period引數控制,單位是秒,預設值是10s。
關於該PING命令究竟是由主節點發給從節點,還是相反,有一些爭議;
因為在Redis的官方文件中,對該引數的註釋中說明是從節點向主節點發送PING命令,如下圖所示:
但是通過原始碼可以看到, PING命令是主節點會向從節點發送.
可能的原因是:程式碼的迭代和註釋的迭代,沒有完全同步。 可能早期是 從發給主,後面改成了主發從,而並沒有配套修改註釋, 就像尼恩的很多程式碼一樣。
2. 從->主:REPLCONF ACK
在命令傳播階段,從節點會向主節點發送REPLCONF ACK命令,頻率是每秒1次;
命令格式為:REPLCONF ACK {offset},其中offset指從節點儲存的複製偏移量。
REPLCONF ACK命令的作用包括:
(1)實時監測主從節點網路狀態:該命令會被主節點用於複製超時的判斷。此外,在主節點中使用info Replication,可以看到其從節點的狀態中的lag值,代表的是主節點上次收到該REPLCONF ACK命令的時間間隔,在正常情況下,該值應該是0或1,如下圖所示:
(2)檢測命令丟失:從節點發送了自身的offset,主節點會與自己的offset對比,如果從節點資料缺失(如網路丟包),主節點會推送缺失的資料(這裡也會利用複製積壓緩衝區)。
注意,offset和複製積壓緩衝區,不僅可以用於部分複製,也可以用於處理命令丟失等情形;區別在於前者是在斷線重連後進行的,而後者是在主從節點沒有斷線的情況下進行的。
(3)輔助保證從節點的數量和延遲:Redis主節點中使用min-slaves-to-write和min-slaves-max-lag引數,來保證主節點在不安全的情況下不會執行寫命令;所謂不安全,是指從節點數量太少,或延遲過高。例如min-slaves-to-write和min-slaves-max-lag分別是3和10,含義是如果從節點數量小於3個,或所有從節點的延遲值都大於10s,則主節點拒絕執行寫命令。而這裡從節點延遲值的獲取,就是通過主節點接收到REPLCONF ACK命令的時間來判斷的,即前面所說的info Replication中的lag值。
建立和啟動Redis群集
建立和啟動Redis群集方式:
- 手工通過節點維護的方式,來建立和啟動Redis群集
- 通過redis-cli --cluster來建立和管理叢集
- 使用官方的redis-trib.rb工具搭建叢集
- 通過其他工具來建立和管理叢集
注意:手動部署Redis群集,這對了解叢集的操作細節方面是非常重要的。
手工通過節點維護的方式,我們需要做的第一件事是在叢集模式下執行幾個空的Redis例項。
我們通過以下步驟來一步一步的搭建Redis的Cluster叢集環境。
手工搭建Redis Cluster主要步驟
1.配置開啟節點
2.meet握手
4.主從關係分配
3.指派槽
具體的搭建過程,請參考
Redis Cluster基本架構
資料分片架構
在單個的 redis節點中,我們都知道redis把資料已 k-v 結構儲存在記憶體中,使得 redis 對資料的讀寫非常之快。Redis Cluster 是去中心化的,它將所有資料分割槽儲存。也就是說當多個 Redis 節點搭建成集群后,每個節點只負責自己應該管理的那部分資料,相互之間儲存的資料是不同的。
Redis Cluster 將全部的鍵空間劃分為16384塊,每一塊空間稱之為槽(slot),又將這些槽及槽所對應的 k-v 劃分給叢集中的每個主節點負責。如下圖:
key -> slot 的演算法選擇上,Redis Cluster 選擇的演算法是 hash(key) mod 16383,即使用CRC16演算法對key進行hash,然後再對16383取模,結果便是對應的slot。
把16384個槽平均分配給節點進行管理,每個節點只能對自己負責的槽進行讀寫操作
由於每個節點之間都彼此通訊,每個節點都知道另外節點負責管理的槽範圍
客戶端訪問任意節點時,對資料key按照CRC16規則進行hash運算,然後對運算結果對16383進行取作,如果餘數在當前訪問的節點管理的槽範圍內,則直接返回對應的資料
如果不在當前節點負責管理的槽範圍內,則會告訴客戶端去哪個節點獲取資料,由客戶端去正確的節點獲取資料
節點間的通訊架構
叢集中會有多個節點,每個節點負責一部分slot以及對應的k-v資料,並且通過直連具體節點的方式與客戶端通訊。那麼問題來了,你向我這裡請求一個key的value,這個key對應的slot並不歸我負責,但我又要需要告訴你MOVED到目標節點,我如何知道這個目標節點是誰呢?
Redis Cluster使用Gossip協議維護節點的元資料資訊,這種協議是P2P模式的,主要指責就是資訊交換。節點間不停地去交換彼此的元資料資訊,那麼總會在一段時間後,大家都知道彼此是誰,負責哪些資料,是否正常工作等等。
節點間資訊交換是依賴於彼此發出的Gossip訊息的。
常用的一般是以下四種訊息:
- meet訊息 會通知接收該訊息的節點,傳送節點要加入當前叢集,接收者進行響應。
- ping訊息 是叢集中的節點定期向叢集中其他節點(部分或全部)傳送的連線檢測以及資訊交換請求,訊息包含傳送節點資訊以及傳送節點知道的其他節點資訊。
- pong訊息 是在節點接收到meet、ping訊息後回覆給傳送節點的響應訊息,告訴傳送方本次通訊正常,訊息包含當前節點狀態。
- fail訊息 是在節點認為叢集內另外某一節點下線後向叢集內所有節點廣播的訊息。
節點的握手訊息
在叢集啟動的過程中,有一個重要的步驟是 節點握手 ,其本質就是在一個節點上向其他所有節點發送meet訊息,訊息中包含當前節點的資訊(節點id,負責槽位,節點標識等等),接收方會將傳送節點資訊儲存至本地的節點列表中。訊息體中還會包含與傳送節點通訊的其他節點資訊(節點標識、節點id、節點ip、port等),接收方也會解析這部分內容,如果本地節點列表中不存在,則會主動向新節點發送meet訊息。接收方處理完訊息後,也會回覆pong訊息給傳送者節點,傳送者也會解析pong訊息更新本地儲存節點資訊。因此,雖然只是在一個節點向其他所有節點發送meet訊息,最後所有節點都會有其他所有節點的資訊。
節點之間會相互通訊,meet操作是節點之間完成相互通訊的基礎,meet操作有一定的頻率和規則
叢集內的心跳訊息
叢集啟動後,叢集中各節點也會定時往 其他部分節點 傳送ping訊息,用來檢測目標節點是否正常以及傳送自己最新的節點負槽位資訊。接收方同樣響應pong訊息,由傳送方更新本地節點資訊。當在與某一節點通訊失敗(故障發現策略後面會說)時,則會主動向叢集內節點廣播fail訊息。考慮到頻繁地交換資訊會加重頻寬(叢集節點越多越明顯)和計算的負擔,Redis Cluster內部的定時任務每秒執行10次,每次遍歷本地節點列表,對最近一次接受到pong訊息時間大於cluster_node_timeout/2的節點立馬傳送ping訊息,此外每秒隨機找5個節點,選裡面最久沒有通訊的節點發送ping訊息。同時 ping 訊息的訊息投攜帶自身節點資訊,訊息體只會攜帶1/10的其他節點資訊,避免訊息過大導致通訊成本過高。
cluster_node_timeout 引數影響傳送訊息的節點數量,調整要綜合考慮故障轉移、槽資訊更新、新節點發現速度等方面。一般頻寬資源特別緊張時,可以適當調大一點這個引數,降低通訊成本。
Redis Cluster的高可用架構
要保證高可用的前提是離不開從節點的,一旦某個主節點因為某種原因不可用後,就需要一個一直默默當備胎的從節點頂上來了。
一般在叢集搭建時最少都需要6個例項,其中3個例項做主節點,各自負責一部分槽位,另外3個例項各自對應一個主節點做其從節點,對主節點的操作進行復制(對於主從複製的細節,前面已經進行詳細說明)。
Redis Cluster在給主節點新增從節點時,不支援slaveof命令,而是通過在從節點上執行命令cluster replicate masterNodeId 。
完整的redis叢集架構圖如下:
Cluster的故障發現也是基於節點通訊的。
每個節點在本地儲存有一個節點列表(其他節點資訊),列表中每個 節點元素除了儲存其ID、ip、port、狀態標識(主從角色、是否下線等等)外,還有最後一次向該節點發送ping訊息的時間、最後一次接收到該節點的pong訊息的時間以及一個儲存其他節點對該節點下線傳播的報告連結串列 。
節點與節點間會定時傳送ping訊息,彼此響應pong訊息,成功後都會更新這個時間。同時每個節點都有定時任務掃描本地節點列表裡這兩個訊息時間,若發現pong響應時間減去ping傳送時間超過cluster-node-timeout配置時間(預設15秒,該引數用來設定節點間通訊的超時時間)後,便會將本地列表中對應節點的狀態標識為PFAIL,認為其有可能下線。
節點間通訊(ping)時會攜帶本地節點列表中部分節點資訊,如果其中包括標記為PFAIL的節點,那麼在訊息接收方解析到該節點時,會找自己本地的節點列表中該節點元素的下線報告連結串列,看是否已經存在傳送節點對於該故障節點的報告,如果有,就更新接收到傳送ping訊息節點對於故障節點的報告的時間,如果沒有,則將本次報告新增進連結串列。 下線報告連結串列的每個元素結構只有兩部分內容,一個是報告本地這個故障節點的傳送節點資訊,一個是本地接收到該報告的時間 (儲存該時間是因為故障報告是有有效期的,避免誤報) 。
由於每個節點的下線報告連結串列都存在於各自的資訊結構中,所以在瀏覽本地節點列表中每個節點元素時,可以清晰地知道,有其他哪些節點跟我說,兄弟,你正在看的這個節點我覺的涼涼了。
故障報告的有效期是 cluster-node-timeout * 2
訊息接收方解析到PFAIL節點,並且更新本地列表中對應節點的故障報告連結串列後,會去檢視該節點的故障報告連結串列中有效的報告節點是否超過所有主節點數的一半。
如果沒超過,便繼續解析ping訊息;如果超過,代表 超過半數的節點認為這個節點可能下線了,當前節點就會將PFAIL節點本地的節點資訊中的狀態標識標記為FAIL ,然後向叢集內廣播一條fail訊息,叢集內的所有節點接收到該fail訊息後,會把各自本地節點列表中該節點的狀態標識修改為FAIL。
在所有節點對其標記未FAIL後,該FAIL節點對應的從節點就會發起轉正流程。在轉正流程完成後,這個節點就會正式下線,等到其恢復後,發現自己的槽已經被分給某個節點,便會將自己轉換成這個節點的從節點並且ping叢集內其他節點,其他節點接到恢復節點的ping訊息後,便會更新其狀態標識。
此外,恢復的節點若發現自己的槽還是由自己負責,就會跟其他節點通訊,其他主節點發現該節點恢復後,就會拒絕其從節點的選舉,最終清除自己的FAIL狀態。
客戶端高可用
客戶端moved重定向和ask重定向
moved重定向
1.每個節點通過通訊都會共享Redis Cluster中槽和叢集中對應節點的關係
2.客戶端向Redis Cluster的任意節點發送命令,接收命令的節點會根據CRC16規則進行hash運算與16383取餘,計算自己的槽和對應節點
3.如果儲存資料的槽被分配給當前節點,則去槽中執行命令,並把命令執行結果返回給客戶端
4.如果儲存資料的槽不在當前節點的管理範圍內,則向客戶端返回moved重定向異常
5.客戶端接收到節點返回的結果,如果是moved異常,則從moved異常中獲取目標節點的資訊
6.客戶端向目標節點發送命令,獲取命令執行結果
需要注意的是:客戶端不會自動找到目標節點執行命令
槽命中:直接返回
槽不命中:moved異常
ask重定向
在對叢集進行擴容和縮容時,需要對槽及槽中資料進行遷移
當客戶端向某個節點發送命令,節點向客戶端返回moved異常,告訴客戶端資料對應的槽的節點資訊
如果此時正在進行叢集擴充套件或者縮空操作,當客戶端向正確的節點發送命令時,槽及槽中資料已經被遷移到別的節點了,就會返回ask,這就是ask重定向機制
步驟:
1.客戶端向目標節點發送命令,目標節點中的槽已經遷移支別的節點上了,此時目標節點會返回ask轉向給客戶端
2.客戶端向新的節點發送Asking命令給新的節點,然後再次向新節點發送命令
3.新節點執行命令,把命令執行結果返回給客戶端
moved異常與ask異常的相同點和不同點
兩者都是客戶端重定向
moved異常:槽已經確定遷移,即槽已經不在當前節點
ask異常:槽還在遷移中
smart智慧客戶端
使用智慧客戶端的首要目標:追求效能
從叢集中選一個可執行節點,使用Cluster slots初始化槽和節點對映
將Cluster slots的結果對映在本地,為每個節點建立JedisPool,相當於為每個redis節點都設定一個JedisPool,然後就可以進行資料讀寫操作
讀寫資料時的注意事項:
每個JedisPool中快取了slot和節點node的關係
key和slot的關係:對key進行CRC16規則進行hash後與16383取餘得到的結果就是槽
JedisCluster啟動時,已經知道key,slot和node之間的關係,可以找到目標節點
JedisCluster對目標節點發送命令,目標節點直接響應給JedisCluster
如果JedisCluster與目標節點連接出錯,則JedisCluster會知道連線的節點是一個錯誤的節點
此時JedisCluster會隨機節點發送命令,隨機節點返回moved異常給JedisCluster
JedisCluster會重新初始化slot與node節點的快取關係,然後向新的目標節點發送命令,目標命令執行命令並向JedisCluster響應
如果命令傳送次數超過5次,則丟擲異常"Too many cluster redirection!"
failover故障發現與轉移
redis叢集自身實現了高可用,Redis Cluster通過ping/pong訊息實現故障發現:不需要sentinel
當叢集內少量節點出現故障時通過自動故障轉移保證叢集可以正常對外提供服務。作為一個完整的叢集,每個負責處理槽的節點應該具有從節點,保證當它出現故障時可以自動進行故障轉移。
首次啟動的節點和被分配槽的節點都是主節點,從節點負責複製主節點槽資訊和相關的資料。
Redis Cluster通過ping/pong訊息不僅能傳遞節點與槽的對應訊息,也能傳遞其他狀態,比如:節點主從狀態,節點故障等
故障發現
故障發現就是通過這種模式來實現,分為:
- 主觀下線
- 客觀下線
故障發現也是通過訊息傳播機制實現的,主要環節包括:
(1)主觀下線(pfail)。
叢集中每個節點都會定期向其他節點發送ping訊息,接收節點回復pong訊息作為響應。如果在cluster-node-timeout時間內通訊一直失敗,則傳送節點會認為接收節點存在故障,把接收節點標記為主觀下線(pfail)狀態。
(2)客觀下線(fail)
當某個節點判斷另一個節點主觀下線後,相應的節點狀態會跟隨訊息在叢集內傳播。當接受節點發現訊息體中含有主觀下線的節點狀態且傳送節點是主節點時,會在本地找到故障節點的ClusterNode結構,更新下線報告連結串列。
主觀下線
某個節點認為另一個節點不可用,'偏見',只代表一個節點對另一個節點的判斷,不代表所有節點的認知
主觀下線流程:
1.節點1定期傳送ping訊息給節點2
2.如果傳送成功,代表節點2正常執行,節點2會響應PONG訊息給節點1,節點1更新與節點2的最後通訊時間
3.如果傳送失敗,則節點1與節點2之間的通訊異常判斷連線,在下一個定時任務週期時,仍然會與節點2傳送ping訊息
4.如果節點1發現與節點2最後通訊時間超過node-timeout,則把節點2標識為pfail狀態
客觀下線
當半數以上持有槽的主節點都標記某節點主觀下線時,可以保證判斷的公平性
叢集模式下,只有主節點(master)才有讀寫許可權和叢集槽的維護許可權,從節點(slave)只有複製的許可權
客觀下線流程:
1.某個節點接收到其他節點發送的ping訊息,如果接收到的ping訊息中包含了其他pfail節點,這個節點會將主觀下線的訊息內容新增到自身的故障列表中,故障列表中包含了當前節點接收到的每一個節點對其他節點的狀態資訊
2.當前節點把主觀下線的訊息內容新增到自身的故障列表之後,會嘗試對故障節點進行客觀下線操作
當某個節點判斷另一個節點主觀下線後,相應的節點狀態會跟隨訊息在叢集內傳播。當接受節點發現訊息體中含有主觀下線的節點狀態且傳送節點是主節點時,會在本地找到故障節點的ClusterNode結構,更新下線報告連結串列。
- 叢集中的節點每次接收到其他節點的pfail狀態,都會嘗試觸發客觀下線。首先統計有效的下線報告數量,當下線報告數量大於槽主節點數量一半時,標記對應故障節點為客觀下線狀態。
- 向叢集廣播一條fail訊息,通知所有的節點將故障節點標記為客觀下線,fail訊息的訊息體只包含故障節點的ID。通知故障節點的從節點觸發故障轉移流程。
struct clusterNode { /* 認為是主觀下線的clusterNode結構 */
list *fail_reports; /* 記錄了所有其他節點對該節點的下線報告 */
};
只有負責槽的主節點參與故障發現決策,因為叢集模式下只有處理槽的主節點才負責讀寫請求和叢集槽等關鍵資訊維護,而從節點只進行主機誒單資料和狀態資訊的複製。
故障列表的週期為:叢集的node-timeout * 2,保證以前的故障訊息不會對週期內的故障訊息造成影響,保證客觀下線的公平性和有效性
Redis節點故障轉移failover(故障恢復)
故障節點變為客觀下線後,如果下線節點是持有槽的主節點則需要在它的從節點中選出一個替換它,從而保證叢集的高可用。
下線主節點的所有從節點承擔故障恢復的義務,當從節點通過內部定時任務發現自身複製的主節點進入客觀下線時,將會觸發故障恢復流程:
(1) 資格檢查
每個從節點都要檢查最後與主節點斷線時間,判斷是否有資格替換故障的主節點。如果從節點與主節點斷線時間超過cluster-node-time * cluster-slave-validity-factor,則當前從節點不具備故障轉移資格。
(2)準備選舉時間
當從節點符合故障轉移資格後,更新觸發故障選舉的時間,只有到達該時間後才能執行後續流程。這裡之所以採用延遲觸發機制,主要是通過對多個從節點使用不同的延遲選舉時間來支援優先順序問題。複製偏移量越大說明從節點延遲越低,那麼它應該具有更高的優先順序來替換故障主節點。
(3)發起選舉
當從節點定時任務檢測到達故障選舉時間(failover_auth_time)到達後,發起選舉流程如下:會先更新配置紀元,再在叢集內廣播選舉訊息,並記錄已傳送過訊息的狀態,保證該從節點在一個配置紀元內只能發起一次選舉。
(4)選舉投票
只有持有槽的主節點才會處理故障選舉訊息,因為每個持有槽的節點在一個配置紀元內都有唯一的一張選票,當接到第一個請求投票的從節點訊息時回覆FAILOVER_AUTH_ACK訊息作為投票,之後相同配置紀元內其他從節點的選舉訊息將忽略。當從節點收集到N/2+1個持有槽的主節點投票時,從節點可以執行替換主機點操作。
(5)替換主節點
當從節點收集到足夠的選票之後,觸發替換主節點操作:
- 當前從節點取消複製變為主節點。
- 執行clusterDelSlot操作撤銷故障主節點負責的槽,並執行clusterAddSlot把這些槽委派給自己。
- 向叢集廣播自己的pong訊息,通知叢集內所有的節點當前從節點變為主節點並接管了故障主節點的槽資訊。
資格檢查
每個從節點都要檢查最後與主節點斷線時間,判斷是否有資格替換故障的主節點。如果從節點與主節點斷線時間超過cluster-node-time * cluster-slave-validity-factor,則當前從節點不具備故障轉移資格。
-
對從節點的資格進行檢查,只有通過檢查的從節點才可以開始進行故障恢復
-
每個從節點檢查與故障主節點的斷線時間
-
超過cluster-node-timeout * cluster-slave-validity-factor數字,則取消資格
-
cluster-node-timeout預設為15秒,cluster-slave-validity-factor預設值為10
-
如果這兩個引數都使用預設值,則每個節點都檢查與故障主節點的斷線時間,如果超過150秒,則這個節點就沒有成為替換主節點的可能性
準備選舉時間
當從節點符合故障轉移資格後,更新觸發故障選舉的時間,只有到達該時間後才能執行後續流程。這裡之所以採用延遲觸發機制,主要是通過對多個從節點使用不同的延遲選舉時間來支援優先順序問題。複製偏移量越大說明從節點延遲越低,那麼它應該具有更高的優先順序來替換故障主節點。
struct clusterState {
mstime_t failover_auth_time; /* 記錄之前或者下次將要執行故障選舉時間 */
int failover_auth_rank; /* 記錄當前從節點排名 */
}
使偏移量最大的從節點具備優先順序成為主節點的條件
發起選舉
當從節點定時任務檢測到達故障選舉時間(failover_auth_time)到達後。
發起選舉流程如下:會先更新配置紀元,再在叢集內廣播選舉訊息,並記錄已傳送過訊息的狀態,保證該從節點在一個配置紀元內只能發起一次選舉。
配置紀元的主要作用:
- 標示叢集內每個主節點的不同版本和當前叢集最大的版本。
- 每次叢集發生重要事件時,這裡的重要事件指出現新的主節點(新加入的或者由從節點轉換而來),從節點競爭選舉。都會遞增叢集全域性的配置紀元並賦值給相關主節點,用於記錄這一關鍵事件。
- 主節點具有更大的配置紀元代表了更新的叢集狀態,因此當節點間進行ping/pong訊息交換時,如出現slots等關鍵資訊不一致時,以配置紀元更大的一方為準,防止過時的訊息狀態汙染叢集。
配置紀元的應用場景有:新節點加入、槽節點對映衝突檢測、從節點投票選舉衝突檢測。
選舉投票
對選舉出來的多個從節點進行投票,選出新的主節點
替換主節點
當從節點收集到足夠的選票之後,觸發替換主節點操作:
- 當前從節點取消複製變為主節點。
- 執行clusterDelSlot操作撤銷故障主節點負責的槽,並執行clusterAddSlot把這些槽委派給自己。
- 向叢集廣播自己的pong訊息,通知叢集內所有的節點當前從節點變為主節點並接管了故障主節點的槽資訊。
故障轉移時間預估
- 主觀下線(pfail)識別時間 = cluster-node-timeout
- 主觀下線狀態訊息傳播時間 <= cluster-node-timeout/2。訊息通訊機制對超過cluster-node-timeout/2未通訊節點會發起ping訊息,訊息體在選擇包含哪些節點時會優先選取下線狀態節點,所以通常這段時間內能夠收集到半數以上主節點的pfail報告從而完成故障發現。
- 從節點轉移時間 <= 1000毫秒。由於存在延遲發起選舉機制,偏移量最大的從節點會最多延遲1秒發起選舉。通常第一次選舉就會成功,所以從節點執行轉移時間在1秒以內。
- 根據以上分析可以預估出故障轉移時間,如下:failover-time(毫秒) <= cluster-node-timeout + cluster-node-timeout/2 + 1000
當節點發現與其他節點最後通訊時間超過cluster-node-timeout/2時會直接傳送ping訊息,適當提高cluster-node-timeout可以降低訊息傳送頻率,但同時cluster-node-timeout還影響故障轉移的速度,因此需要根據自身業務場景兼顧二者的平衡。
故障轉移演練
對某一個主節點執行kill -9 {pid}來模擬宕機的情況
開發運維常見的問題
叢集完整性
cluster-require-full-coverage預設為yes,即是否叢集中的所有節點都是線上狀態且16384個槽都處於服務狀態時,叢集才會提供服務
叢集中16384個槽全部處於服務狀態,保證叢集完整性
當某個節點故障或者正在故障轉移時獲取資料會提示:(error)CLUSTERDOWN The cluster is down
建議把cluster-require-full-coverage設定為no
頻寬消耗
Redis Cluster節點之間會定期交換Gossip訊息,以及做一些心跳檢測
官方建議Redis Cluster節點數量不要超過1000個,當叢集中節點數量過多時,會產生不容忽視的頻寬消耗
訊息傳送頻率:節點發現與其他節點最後通訊時間超過cluster-node-timeout /2時,會直接傳送PING訊息
訊息資料量:slots槽陣列(2kb空間)和整個叢集1/10的狀態資料(10個節點狀態資料約為1kb)
節點部署的機器規模:叢集分佈的機器越多且每臺機器劃分的節點數越均勻,則叢集內整體的可用頻寬越高
頻寬優化:
避免使用'大'叢集:避免多業務使用一個叢集,大業務可以多叢集
cluster-node-timeout:頻寬和故障轉移速度的均衡
儘量均勻分配到多機器上:保證高可用和頻寬
Pub/Sub廣播
在任意一個cluster節點執行publish,則釋出的訊息會在叢集中傳播,叢集中的其他節點都會訂閱到訊息,這樣節點的頻寬的開銷會很大
publish在叢集每個節點廣播,加重頻寬
解決辦法:需要使用Pub/Sub時,為了保證高可用,可以單獨開啟一套Redis Sentinel
叢集傾斜
對於分散式資料庫來說,存在傾斜問題是比較常見的
叢集傾斜也就是各個節點使用的記憶體不一致
資料傾斜原因
1.節點和槽分配不均,如果使用redis-trib.rb工具構建叢集,則出現這種情況的機會不多
redis-trib.rb info ip:port檢視節點,槽,鍵值分佈
redis-trib.rb rebalance ip:port進行均衡(謹慎使用)
2.不同槽對應鍵值數量差異比較大
CRC16演算法正常情況下比較均勻
可能存在hash_tag
cluster countkeysinslot {slot}獲取槽對應鍵值個數
3.包含bigkey:例如大字串,幾百萬的元素的hash,set等
在從節點:redis-cli --bigkeys
優化:優化資料結構
4.記憶體相關配置不一致
hash-max-ziplist-value:滿足一定條件情況下,hash可以使用ziplist
set-max-intset-entries:滿足一定條件情況下,set可以使用intset
在一個叢集內有若干個節點,當其中一些節點配置上面兩項優化,另外一部分節點沒有配置上面兩項優化
當叢集中儲存hash或者set時,就會造成節點資料不均勻
優化:定期檢查配置一致性
5.請求傾斜:熱點key
重要的key或者bigkey
Redis Cluster某個節點有一個非常重要的key,就會存在熱點問題
叢集傾斜優化:
避免bigkey
熱鍵不要用hash_tag
當一致性不高時,可以用本地快取+ MQ(訊息佇列)
資料遷移
官方遷移工具:redis-trib.rb和import
只能從單機遷移到叢集
不支援線上遷移:source需要停寫
不支援斷點續傳
單執行緒遷移:影響深度
線上遷移:
唯品會:redis-migrate-tool
豌豆莢:redis-port
超高併發程式設計優化方案
- Redis Cluster不支援使用scan命令掃描所有節點
- 多節點命令就是在在所有節點上都執行一條命令
- 批量操作優化
- 叢集讀寫分離
叢集讀寫分離
只讀連線:叢集模式下,從節點不接受任何讀寫請求
當向從節點執行讀請求時,重定向到負責槽的主節點
readonly命令可以讀:連線級別命令,當連線斷開之後,需要再次執行readonly命令
讀寫分離:
同樣的問題:複製延遲,讀取過期資料,從節點故障
修改客戶端:cluster slaves {nodeId}
批量操作優化
- 序列mget
- 序列IO
- 並行IO
- hash_tag
序列mget
定義for迴圈,遍歷所有的key,分別去所有的Redis節點中獲取值並進行彙總,簡單,但是效率不高,需要n次網路時間
序列IO
對序列mget進行優化,在客戶端本地做內聚,對每個key進行CRC16hash,然後與16383取餘,就可以知道哪個key對應的是哪個槽
本地已經快取了槽與節點的對應關係,然後對key按節點進行分組,成立子集,然後使用pipeline把命令傳送到對應的node,需要nodes次網路時間,大大減少了網路時間開銷
並行IO
並行IO是對序列IO的一個優化,把key分組之後,根據節點數量啟動對應的執行緒數,根據多執行緒模式並行向node節點請求資料,只需要1次網路時間
hash_tag
將key進行hash_tag的包裝,然後把tag用大括號括起來,保證所有的key只向一個node請求資料,這樣執行類似mget命令只需要去一個節點獲取資料即可,效率更高
四種高併發優化方案優缺點分析
參考文獻
https://www.cnblogs.com/zjxiang/p/12484474.html
https://www.cnblogs.com/zjxiang/p/12484474.html
https://blog.csdn.net/crazymakercircle/article/details/116110302
https://blog.csdn.net/crazymakercircle/article/details/116110302
https://www.cnblogs.com/kismetv/p/9236731.html#t31