redis系統深度學習五:集羣擴容與故障轉移


前言

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

Redis集羣解決了上述問題,實現了較爲完善的高可用方案。本文將詳細介紹集羣,主要內容包括:集羣的做用;集羣的搭建方法及設計方案;集羣的基本原理;客戶端訪問集羣的方法;以及其餘實踐中須要的集羣知識(集羣擴容、故障轉移、參數優化等)。node


目錄


1、集羣的做用

2、集羣的搭建

     1. 執行Redis命令搭建集羣linux

     2. 使用Ruby腳本搭建集羣c++

     3. 集羣方案設計redis

3、集羣的基本原理

     1. 數據分區方案算法

     2. 節點通訊機制數據庫

     3. 數據結構編程

     4. 集羣命令的實現數組

4、客戶端訪問集羣

     1. redis-cli緩存

     2. Smart客戶端

5、實踐須知

     1. 集羣伸縮

     2. 故障轉移

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

     4. Hash Tag

     5. 參數優化

     6. redis-trib.rb

 

1、集羣的做用

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

集羣由多個節點(Node)組成,Redis的數據分佈在這些節點中。集羣中的節點分爲主節點和從節點:只有主節點負責讀寫請求和集羣信息的維護;從節點只進行主節點數據和狀態信息的複製。

集羣的做用,能夠概括爲兩點:

一、數據分區:數據分區(或稱數據分片)是集羣最核心的功能。

集羣將數據分散到多個節點,一方面突破了Redis單機內存大小的限制,存儲容量大大增長;另外一方面每一個主節點均可以對外提供讀服務和寫服務,大大提升了集羣的響應能力。

Redis單機內存大小受限問題,在介紹持久化和主從複製時都有說起;例如,若是單機內存太大,bgsave和bgrewriteaof的fork操做可能致使主進程阻塞,主從環境下主機切換時可能致使從節點長時間沒法提供服務,全量複製階段主節點的複製緩衝區可能溢出……。

二、高可用:集羣支持主從複製和主節點的自動故障轉移(與哨兵相似);當任一節點發生故障時,集羣仍然能夠對外提供服務。

本文內容基於Redis 3.0.6。

2、集羣的搭建

這一部分咱們將搭建一個簡單的集羣:共6個節點,3主3從。方便起見:全部節點在同一臺服務器上,以端口號進行區分;配置從簡。3個主節點端口號:7000/7001/7002,對應的從節點端口號:8000/8001/8002。

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

1. 執行Redis命令搭建集羣

集羣的搭建能夠分爲四步:(1)啓動節點:將節點以集羣模式啓動,此時節點是獨立的,並無創建聯繫;(2)節點握手:讓獨立的節點連成一個網絡;(3)分配槽:將16384個槽分配給主節點;(4)指定主從關係:爲從節點指定主節點。

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

(1)啓動節點

集羣節點的啓動仍然是使用redis-server命令,但須要使用集羣模式啓動。下面是7000節點的配置文件(只列出了節點正常工做關鍵配置,其餘配置(如開啓AOF)能夠參照單機節點進行):

#redis-7000.conf

port 7000

cluster-enabled yes

cluster-config-file "node-7000.conf"

logfile "log-7000.log"

dbfilename "dump-7000.rdb"

daemonize yes


cluster-enabled yes:Redis實例能夠分爲單機模式(standalone)和集羣模式(cluster);cluster-enabled yes能夠啓動集羣模式。在單機模式下啓動的Redis實例,若是執行info server命令,能夠發現redis_mode一項爲standalone,以下圖所示:其中的cluster-enabled和cluster-config-file是與集羣相關的配置。

集羣模式下的節點,其redis_mode爲cluster,以下圖所示:

cluster-config-file:該參數指定了集羣配置文件的位置。每一個節點在運行過程當中,會維護一份集羣配置文件;每當集羣信息發生變化時(如增減節點),集羣內全部節點會將最新信息更新到該配置文件;當節點重啓後,會從新讀取該配置文件,獲取集羣信息,能夠方便的從新加入到集羣中。也就是說,當Redis節點以集羣模式啓動時,會首先尋找是否有集羣配置文件,若是有則使用文件中的配置啓動,若是沒有,則初始化配置並將配置保存到文件中。集羣配置文件由Redis節點維護,不須要人工修改。

編輯好配置文件後,使用redis-server命令啓動該節點:

redis-server redis-7000.conf


節點啓動之後,經過cluster nodes命令能夠查看節點的狀況,以下圖所示。

其中返回值第一項表示節點id,由40個16進制字符串組成,節點id與 主從複製 一文中提到的runId不一樣:Redis每次啓動runId都會從新建立,可是節點id只在集羣初始化時建立一次,而後保存到集羣配置文件中,之後節點從新啓動時會直接在集羣配置文件中讀取。

其餘節點使用相同辦法啓動,再也不贅述。須要特別注意,在啓動節點階段,節點是沒有主從關係的,所以從節點不須要加slaveof配置。

(2)節點握手

節點啓動之後是相互獨立的,並不知道其餘節點存在;須要進行節點握手,將獨立的節點組成一個網絡。

節點握手使用cluster meet {ip} {port}命令實現,例如在7000節點中執行cluster meet 192.168.72.128 7001,能夠完成7000節點和7001節點的握手;注意ip使用的是局域網ip而不是localhost或127.0.0.1,是爲了其餘機器上的節點或客戶端也能夠訪問。此時再使用cluster nodes查看:

在7001節點下也能夠相似查看:

同理,在7000節點中使用cluster meet命令,能夠將全部節點加入到集羣,完成節點握手:

cluster meet 192.168.72.128 7002

cluster meet 192.168.72.128 8000

cluster meet 192.168.72.128 8001

cluster meet 192.168.72.128 8002


執行完上述命令後,能夠看到7000節點已經感知到了全部其餘節點:

經過節點之間的通訊,每一個節點均可以感知到全部其餘節點,以8000節點爲例:

(3)分配槽

在Redis集羣中,藉助槽實現數據分區,具體原理後文會介紹。集羣有16384個槽,槽是數據管理和遷移的基本單位。當數據庫中的16384個槽都分配了節點時,集羣處於上線狀態(ok);若是有任意一個槽沒有分配節點,則集羣處於下線狀態(fail)。

cluster info命令能夠查看集羣狀態,分配槽以前狀態爲fail:

分配槽使用cluster addslots命令,執行下面的命令將槽(編號0-16383)所有分配完畢:

redis-cli -p 7000 cluster addslots {0..5461}

redis-cli -p 7001 cluster addslots {5462..10922}

redis-cli -p 7002 cluster addslots {10923..16383}


此時查看集羣狀態,顯示全部槽分配完畢,集羣進入上線狀態:

(4)指定主從關係

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

經過cluster nodes得到幾個主節點的節點id後,執行下面的命令爲每一個從節點指定主節點:

redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae

redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4

redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1


此時執行cluster nodes查看各個節點的狀態,能夠看到主從關係已經創建。

至此,集羣搭建完畢。

2. 使用Ruby腳本搭建集羣

在{REDIS_HOME}/src目錄下能夠看到redis-trib.rb文件,這是一個Ruby腳本,能夠實現自動化的集羣搭建。

(1)安裝Ruby環境

以Ubuntu爲例,以下操做便可安裝Ruby環境:

apt-get install ruby #安裝ruby環境

gem install redis #gem是ruby的包管理工具,該命令能夠安裝ruby-redis依賴


與第一種方法中的「啓動節點」徹底相同。(2)啓動節點

(3)搭建集羣

redis-trib.rb腳本提供了衆多命令,其中create用於搭建集羣,使用方法以下:

./redis-trib.rb create --replicas 1 192.168.72.128:7000 192.168.72.128:7001 192.168.72.128:7002 192.168.72.128:8000 192.168.72.128:8001 192.168.72.128:8002


執行建立命令後,腳本會給出建立集羣的計劃,以下圖所示;計劃包括哪些是主節點,哪些是從節點,以及如何分配槽。其中:--replicas=1表示每一個主節點有1個從節點;後面的多個{ip:port}表示節點地址,前面的作主節點,後面的作從節點。使用redis-trib.rb搭建集羣時,要求節點不能包含任何槽和數據。

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

至此,集羣搭建完畢。

3. 集羣方案設計

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

(1)高可用要求:根據故障轉移的原理,至少須要3個主節點才能完成故障轉移,且3個主節點不該在同一臺物理機上;每一個主節點至少須要1個從節點,且主從節點不該在一臺物理機上;所以高可用集羣至少包含6個節點。

(2)數據量和訪問量:估算應用須要的數據量和總訪問量(考慮業務發展,留有冗餘),結合每一個主節點的容量和能承受的訪問量(能夠經過benchmark獲得較準確估計),計算須要的主節點數量。

(3)節點數量限制:Redis官方給出的節點數量限制爲1000,主要是考慮節點間通訊帶來的消耗。在實際應用中應儘可能避免大集羣;若是節點數量不足以知足應用對Redis數據量和訪問量的要求,能夠考慮:(1)業務分割,大集羣分爲多個小集羣;(2)減小沒必要要的數據;(3)調整數據過時策略等。

(4)適度冗餘:Redis能夠在不影響集羣服務的狀況下增長節點,所以節點數量適當冗餘便可,不用太大。

推薦一下本身的linuxC/C++交流羣:973961276!整理了一些我的以爲比較好的學習書籍、視頻資料以及大廠面經視頻,有須要的小夥伴能夠進羣獲取哦!

3、集羣的基本原理

上一章介紹了集羣的搭建方法和設計方案,下面將進一步深刻,介紹集羣的原理。集羣最核心的功能是數據分區,所以首先介紹數據的分區規則;而後介紹集羣實現的細節:通訊機制和數據結構;最後以cluster meet(節點握手)、cluster addslots(槽分配)爲例,說明節點是如何利用上述數據結構和通訊機制實現集羣命令的。

1. 數據分區方案

數據分區有順序分區、哈希分區等,其中哈希分區因爲其自然的隨機性,使用普遍;集羣的分區方案即是哈希分區的一種。

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

衡量數據分區方法好壞的標準有不少,其中比較重要的兩個因素是(1)數據分佈是否均勻(2)增長或刪減節點對數據分佈的影響。因爲哈希的隨機性,哈希分區基本能夠保證數據分佈均勻;所以在比較哈希分區方案時,重點要看增減節點對數據分佈的影響。

(1)哈希取餘分區

哈希取餘分區思路很是簡單:計算key的hash值,而後對節點數量進行取餘,從而決定數據映射到哪一個節點上。該方案最大的問題是,當新增或刪減節點時,節點數量發生變化,系統中全部的數據都須要從新計算映射關係,引起大規模數據遷移。

(2)一致性哈希分區

一致性哈希算法將整個哈希值空間組織成一個虛擬的圓環,以下圖所示,範圍爲0-2^32-1;對於每一個數據,根據key計算hash值,肯定數據在環上的位置,而後今後位置沿環順時針行走,找到的第一臺服務器就是其應該映射到的服務器。

圖片來源: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集羣將數據映射到實際節點的過程:

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

(1)Redis對數據的特徵值(通常是key)計算哈希值,使用的算法是CRC16。

(2)根據哈希值,計算數據屬於哪一個槽。

(3)根據槽與節點的映射關係,計算數據屬於哪一個節點。

2. 節點通訊機制

集羣要做爲一個總體工做,離不開節點之間的通訊。

兩個端口

在哨兵系統中,節點分爲數據節點和哨兵節點:前者存儲數據,後者實現額外的控制功能。在集羣中,沒有數據節點與非數據節點之分:全部的節點都存儲數據,也都參與集羣狀態的維護。爲此,集羣中的每一個節點,都提供了兩個TCP端口:

  • 普通端口:即咱們在前面指定的端口(7000等)。普通端口主要用於爲客戶端提供服務(與單機節點相似);但在節點間數據遷移時也會使用。
  • 集羣端口:端口號是普通端口+10000(10000是固定值,沒法改變),如7000節點的集羣端口爲17000。集羣端口只用於節點之間的通訊,如搭建集羣、增減節點、故障轉移等操做時節點間的通訊;不要使用客戶端鏈接集羣接口。爲了保證集羣能夠正常工做,在配置防火牆時,要同時開啓普通端口和集羣端口。

Gossip協議

節點間通訊,按照通訊協議能夠分爲幾種類型:單對單、廣播、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命令。

3. 數據結構

節點須要專門的數據結構來存儲集羣的狀態。所謂集羣的狀態,是一個比較大的概念,包括:集羣是否處於上線狀態、集羣中有哪些節點、節點是否可達、節點的主從狀態、槽的分佈……

節點爲了存儲集羣狀態而提供的數據結構中,最關鍵的是clusterNode和clusterState結構:前者記錄了一個節點的狀態,後者記錄了集羣做爲一個總體的狀態。

clusterNode

clusterNode結構保存了一個節點的當前狀態,包括建立時間、節點id、ip和端口號等。每一個節點都會用一個clusterNode結構記錄本身的狀態,併爲集羣內全部其餘節點都建立一個clusterNode結構來記錄節點狀態。

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

typedef struct clusterNode {

    //節點建立時間

    mstime_t ctime;



    //節點id

    char name[REDIS_CLUSTER_NAMELEN];



    //節點的ip和端口號

    char ip[REDIS_IP_STR_LEN];

    int port;



    //節點標識:整型,每一個bit都表明了不一樣狀態,如節點的主從狀態、是否在線、是否在握手等

    int flags;



    //配置紀元:故障轉移時起做用,相似於哨兵的配置紀元

    uint64_t configEpoch;



    //槽在該節點中的分佈:佔用16384/8個字節,16384個比特;每一個比特對應一個槽:比特值爲1,則該比特對應的槽在節點中;比特值爲0,則該比特對應的槽不在節點中

    unsigned char slots[16384/8];



    //節點中槽的數量

    int numslots;



    …………



} clusterNode;


clusterState除了上述字段,clusterNode還包含節點鏈接、主從複製、故障發現和轉移須要的信息等。

clusterState結構保存了在當前節點視角下,集羣所處的狀態。主要字段包括:

typedef struct clusterState {



    //自身節點

    clusterNode *myself;



    //配置紀元

    uint64_t currentEpoch;



    //集羣狀態:在線仍是下線

    int state;



    //集羣中至少包含一個槽的節點數量

    int size;



    //哈希表,節點名稱->clusterNode節點指針

    dict *nodes;

  

    //槽分佈信息:數組的每一個元素都是一個指向clusterNode結構的指針;若是槽尚未分配給任何節點,則爲NULL

    clusterNode *slots[16384];



    …………

     

} clusterState;


4. 集羣命令的實現除此以外,clusterState還包括故障轉移、槽遷移等須要的信息。

 

這一部分將以cluster meet(節點握手)、cluster addslots(槽分配)爲例,說明節點是如何利用上述數據結構和通訊機制實現集羣命令的。

cluster meet

假設要向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。

cluster addslots

集羣中槽的分配信息,存儲在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節點

4、客戶端訪問集羣

在集羣中,數據分佈在不一樣的節點中,客戶端經過某節點訪問數據時,數據可能不在該節點中;下面介紹集羣是如何處理這個問題的。

1. redis-cli

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

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

集羣提供的cluster keyslot命令也是使用上述公式實現,如:

(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-cli經過-c指定了集羣模式,若是沒有指定,redis-cli沒法處理MOVED錯誤:

2. Smart客戶端

redis-cli這一類客戶端稱爲Dummy客戶端,由於它們在執行命令前不知道數據在哪一個節點,須要藉助MOVED錯誤從新定向。與Dummy客戶端相對應的是Smart客戶端。

Smart客戶端(以Java的JedisCluster爲例)的基本原理:

(1)JedisCluster初始化時,在內部維護slot->node的緩存,方法是鏈接任一節點,執行cluster slots命令,該命令返..回以下所示:

(2)此外,JedisCluster爲每一個節點建立鏈接池(即JedisPool)。

(3)當執行命令時,JedisCluster根據key->slot->node選擇須要鏈接的節點,發送命令。若是成功,則命令執行完畢。若是執行失敗,則會隨機選擇其餘節點進行重試,並在出現MOVED錯誤時,使用cluster slots從新同步slot->node的映射關係。

下面代碼演示瞭如何使用JedisCluster訪問集羣(未考慮資源釋放、異常處理等):

public static void test() {

   Set<HostAndPort> nodes = new HashSet<>();

   nodes.add(new HostAndPort("192.168.72.128", 7000));

   nodes.add(new HostAndPort("192.168.72.128", 7001));

   nodes.add(new HostAndPort("192.168.72.128", 7002));

   nodes.add(new HostAndPort("192.168.72.128", 8000));

   nodes.add(new HostAndPort("192.168.72.128", 8001));

   nodes.add(new HostAndPort("192.168.72.128", 8002));

   JedisCluster cluster = new JedisCluster(nodes);

   System.out.println(cluster.get("key1"));

   cluster.close();

}


(1)JedisCluster中已經包含全部節點的鏈接池,所以JedisCluster要使用單例。注意事項以下:

(2)客戶端維護了slot->node映射關係以及爲每一個節點建立了鏈接池,當節點數量較多時,應注意客戶端內存資源和鏈接資源的消耗。

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

5、實踐須知

前面介紹了集羣正常運行和訪問的方法和原理,下面是一些重要的補充內容。

1. 集羣伸縮

實踐中經常須要對集羣進行伸縮,如訪問量增大時的擴容操做。Redis集羣能夠在不影響對外服務的狀況下實現伸縮;伸縮的核心是槽遷移:修改槽與節點的對應關係,實現槽(即數據)在節點之間的移動。例如,若是槽均勻分佈在集羣的3個節點中,此時增長一個節點,則須要從3個節點中分別拿出一部分槽給新節點,從而實現槽在4個節點中的均勻分佈。

增長節點

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

(1)啓動節點:方法參見集羣搭建

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

redis-trib.rb add-node 192.168.72.128:7003 192.168.72.128 7000

redis-trib.rb add-node 192.168.72.128:8003 192.168.72.128 7000


待遷移的槽數量:16384個槽均分給4個節點,每一個節點4096個槽,所以待遷移槽數量爲4096(3)遷移槽:推薦使用redis-trib.rb的reshard工具實現。reshard自動化程度很高,只須要輸入redis-trib.rb reshard ip:port (ip和port能夠是集羣中的任一節點),而後按照提示輸入如下信息,槽遷移會自動完成:

    • 目標節點id:7003節點的id
    • 源節點的id:7000/7001/7002節點的id

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

減小節點

假設要下線7000/8000節點,能夠分爲兩步:

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

(2)下線節點:使用redis-trib.rb del-node工具;應先下線從節點再下線主節點,由於若主節點先下線,從節點會被指向其餘主節點,形成沒必要要的全量複製。

redis-trib.rb del-node 192.168.72.128:7001 {節點8000的id}

redis-trib.rb del-node 192.168.72.128:7001 {節點7000的id}


集羣伸縮的核心是槽遷移。在槽遷移過程當中,若是客戶端向源節點發送命令,源節點執行流程以下:ASK錯誤

圖片來源:《Redis設計與實現》

客戶端收到ASK錯誤後,從中讀取目標節點的地址信息,並向目標節點從新發送請求,就像收到MOVED錯誤時同樣。可是兩者有很大區別:ASK錯誤說明數據正在遷移,不知道什麼時候遷移完成,所以重定向是臨時的,SMART客戶端不會刷新slots緩存;MOVED錯誤重定向則是(相對)永久的,SMART客戶端會刷新slots緩存。

2. 故障轉移

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

與哨兵同樣,集羣只實現了主節點的故障轉移;從節點故障時只會被下線,不會進行故障轉移。所以,使用集羣時,應謹慎使用讀寫分離技術,由於從節點故障會致使讀服務不可用,可用性變差。

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

節點數量:在故障轉移階段,須要由主節點投票選出哪一個從節點成爲新的主節點;從節點選舉勝出須要的票數爲N/2+1;其中N爲主節點數量(包括故障主節點),但故障主節點實際上不能投票。所以爲了可以在故障發生時順利選出從節點,集羣中至少須要3個主節點(且部署在不一樣的物理機上)。

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

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

cluster-node-timeout的默認值爲15000ms(15s),所以故障轉移時間會在20s量級。

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

因爲集羣中的數據分佈在不一樣節點中,致使一些功能受限,包括:

(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)複製結構:只支持一層複製結構,不支持嵌套。

4. Hash Tag

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,能夠將全部產品信息放到同一個槽中,便於操做。

5. 參數優化

cluster_node_timeout

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

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

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

cluster-require-full-coverage

前面提到,只有當16384個槽所有分配完畢時,集羣才能上線。這樣作是爲了保證集羣的完整性,但同時也帶來了新的問題:當主節點發生故障而故障轉移還沒有完成,原主節點中的槽不在任何節點中,此時會集羣處於下線狀態,沒法響應客戶端的請求。

cluster-require-full-coverage參數能夠改變這一設定:若是設置爲no,則當槽沒有徹底分配時,集羣仍能夠上線。參數默認值爲yes,若是應用對可用性要求較高,能夠修改成no,但須要本身保證槽所有分配。

6. redis-trib.rb

redis-trib.rb提供了衆多實用工具:建立集羣、增減節點、槽遷移、檢查完整性、數據從新平衡等;經過help命令能夠查看詳細信息。在實踐中若是能使用redis-trib.rb工具則儘可能使用,不但方便快捷,還能夠大大下降出錯機率。


好了,文章就寫到這吧,最後推薦一下不錯的c/c++ linux服務器高級架構師學習課程,天天晚上八點都會有直播,學習編程的朋友不妨點一下免費報名,上課的時候會有通知,有時間的時候就能夠去聽聽哦

相關文章
相關標籤/搜索