1. 程式人生 > >redis cluster介紹

redis cluster介紹

講解分散式資料儲存的核心演算法,資料分佈的演算法

hash演算法 -> 一致性hash演算法(memcached) -> redis cluster,hash slot演算法

一、概述

  1、我們的memcache客戶端(這裡我看的spymemcache的原始碼),使用了一致性hash演算法ketama進行資料儲存節點的選擇。與常規的hash演算法思路不同,只是對我們要儲存資料的key進行hash計算,分配到不同節點儲存。一致性hash演算法是對我們要儲存資料的伺服器進行hash計算,進而確認每個key的儲存位置。

 2、常規hash演算法的應用以及其弊端

    最常規的方式莫過於hash取模的方式。比如叢集中可用機器適量為N,那麼key值為K的的資料請求很簡單的應該路由到hash(K) mod N對應的機器。的確,這種結構是簡單的,也是實用的。但是在一些高速發展的web系統中,這樣的解決方案仍有些缺陷。隨著系統訪問壓力的增長,快取系統不得不通過增加機器節點的方式提高叢集的相應速度和資料承載量。增加機器意味著按照hash取模的方式,在增加機器節點的這一時刻,大量的快取命不中,快取資料需要重新建立,甚至是進行整體的快取資料遷移,瞬間會給DB帶來極高的系統負載,設定導致DB伺服器宕機。

  3、設計分散式cache系統時,一致性hash演算法可以幫我們解決哪些問題?

   分散式快取設計核心點:在設計分散式cache系統的時候,我們需要讓key的分佈均衡,並且在增加cache server後,cache的遷移做到最少。

   這裡提到的一致性hash演算法ketama的做法是:選擇具體的機器節點不在只依賴需要快取資料的key的hash本身了,而是機器節點本身也進行了hash運算。

 

二、一致性雜湊演算法情景描述(轉載)

如假設某雜湊函式H的值空間為0-2^32-1(即雜湊值是一個32位無符號整形),整個雜湊空間環如下:

整個空間按順時針方向組織。0和232-1在零點中方向重合。

下一步將各個伺服器使用Hash進行一個雜湊,具體可以選擇伺服器的ip或主機名作為關鍵字進行雜湊,這樣每臺機器就能確定其在雜湊環上的位置,這裡假設將上文中四臺伺服器使用ip地址雜湊後在環空間的位置如下:

  

接下來使用如下演算法定位資料訪問到相應伺服器:將資料key使用相同的函式Hash計算出雜湊值,並確定此資料在環上的位置,從此位置沿環順時針“行走”,第一臺遇到的伺服器就是其應該定位到的伺服器。

  例如我們有Object A、Object B、Object C、Object D四個資料物件,經過雜湊計算後,在環空間上的位置如下:

根據一致性雜湊演算法,資料A會被定為到Node A上,B被定為到Node B上,C被定為到Node C上,D被定為到Node D上。

下面分析一致性雜湊演算法的容錯性和可擴充套件性。現假設Node C不幸宕機,可以看到此時物件A、B、D不會受到影響,只有C物件被重定位到Node D。一般的,在一致性雜湊演算法中,如果一臺伺服器不可用,則受影響的資料僅僅是此伺服器到其環空間中前一臺伺服器(即沿著逆時針方向行走遇到的第一臺伺服器)之間資料,其它不會受到影響。

下面考慮另外一種情況,如果在系統中增加一臺伺服器Node X,如下圖所示:

此時物件Object A、B、D不受影響,只有物件C需要重定位到新的Node X 。一般的,在一致性雜湊演算法中,如果增加一臺伺服器,則受影響的資料僅僅是新伺服器到其環空間中前一臺伺服器(即沿著逆時針方向行走遇到的第一臺伺服器)之間資料,其它資料也不會受到影響。

綜上所述,一致性雜湊演算法對於節點的增減都只需重定位環空間中的一小部分資料,具有較好的容錯性和可擴充套件性。

Consistent Hashing最大限度地抑制了hash鍵的重新分佈。另外要取得比較好的負載均衡的效果,往往在伺服器數量比較少的時候需要增加虛擬節點來保證伺服器能均勻的分佈在圓環上。因為使用一般的hash方法,伺服器的對映地點的分佈非常不均勻。使用虛擬節點的思想,為每個物理節點(伺服器)在圓上分配100~200個點。這樣就能抑制分佈不均勻,最大限度地減小伺服器增減時的快取重新分佈。使用者資料對映在虛擬節點上,就表示使用者資料真正儲存位置是在該虛擬節點代表的實際物理伺服器上。
下面有一個圖描述了需要為每臺物理伺服器增加的虛擬節點。


圖三 

x軸表示的是需要為每臺物理伺服器擴充套件的虛擬節點倍數(scale),y軸是實際物理伺服器數,可以看出,當物理伺服器的數量很小時,需要更大的虛擬節點,反之則需要更少的節點,從圖上可以看出,在物理伺服器有10臺時,差不多需要為每臺伺服器增加100~200個虛擬節點才能達到真正的負載均衡。

三、以spymemcache原始碼來演示虛擬節點應用

1、上邊描述的一致性Hash演算法有個潛在的問題是:
     (1)、將節點hash後會不均勻地分佈在環上,這樣大量key在尋找節點時,會存在key命中各個節點的概率差別較大,無法實現有效的負載均衡。
     (2)、如有三個節點Node1,Node2,Node3,分佈在環上時三個節點挨的很近,落在環上的key尋找節點時,大量key順時針總是分配給Node2,而其它兩個節點被找到的概率都會很小。

2、這種問題的解決方案可以有:
     改善Hash演算法,均勻分配各節點到環上;[引文]使用虛擬節點的思想,為每個物理節點(伺服器)在圓上分配100~200個點。這樣就能抑制分佈不均勻,最大限度地減小伺服器增減時的快取重新分佈。使用者資料對映在虛擬節點上,就表示使用者資料真正儲存位置是在該虛擬節點代表的實際物理伺服器上。

在檢視Spy Memcached client時,發現它採用一種稱為Ketama的Hash演算法,以虛擬節點的思想,解決Memcached的分散式問題。 

3、原始碼說明

該client採用TreeMap儲存所有節點,模擬一個環形的邏輯關係。在這個環中,節點之前是存在順序關係的,所以TreeMap的key必須實現Comparator介面。
那節點是怎樣放入這個環中的呢?

protected void setKetamaNodes(List<MemcachedNode> nodes) {
    TreeMap<Long, MemcachedNode> newNodeMap = new TreeMap<Long, MemcachedNode>();
    int numReps= config.getNodeRepetitions();
    for(MemcachedNode node : nodes) {
        // Ketama does some special work with md5 where it reuses chunks.
        if(hashAlg == HashAlgorithm.KETAMA_HASH) {
            for(int i=0; i<numReps / 4; i++) {
                byte[] digest=HashAlgorithm.computeMd5(config.getKeyForNode(node, i));
                for(int h=0;h<4;h++) {
                    Long k = ((long)(digest[3+h*4]&0xFF) << 24)
                        | ((long)(digest[2+h*4]&0xFF) << 16)
                        | ((long)(digest[1+h*4]&0xFF) << 8)
                        | (digest[h*4]&0xFF);
                    newNodeMap.put(k, node);
                    getLogger().debug("Adding node %s in position %d", node, k);
                }

            }
        } else {
            for(int i=0; i<numReps; i++) {
                newNodeMap.put(hashAlg.hash(config.getKeyForNode(node, i)), node);
            }
        }
    }
    assert newNodeMap.size() == numReps * nodes.size();
    ketamaNodes = newNodeMap;

上面的流程大概可以這樣歸納:四個虛擬結點為一組,以getKeyForNode方法得到這組虛擬節點的name,Md5編碼後,每個虛擬結點對應Md5碼16個位元組中的4個,組成一個long型數值,做為這個虛擬結點在環中的惟一key。第10行k為什麼是Long型的呢?就是因為Long型實現了Comparator介面。

處理完正式結點在環上的分佈後,可以開始key在環上尋找節點的遊戲了。
對於每個key還是得完成上面的步驟:計算出Md5,根據Md5的位元組陣列,通過Kemata Hash演算法得到key在這個環中的位置。

MemcachedNode getNodeForKey(long hash) {  
    final MemcachedNode rv;  
    if(!ketamaNodes.containsKey(hash)) {  
        // Java 1.6 adds a ceilingKey method, but I'm still stuck in 1.5  
        // in a lot of places, so I'm doing this myself.  
        SortedMap<Long, MemcachedNode> tailMap=getKetamaNodes().tailMap(hash);  
        if(tailMap.isEmpty()) {  
            hash=getKetamaNodes().firstKey();  
        } else {  
            hash=tailMap.firstKey();  
        }  
    }  
    rv=getKetamaNodes().get(hash);  
    return rv;  
}  

上邊程式碼的實現就是在環上順時針查詢,沒找到就去的第一個,然後就知道對應的物理節點了。

四、應用場景分析

1、memcache的add方法:通過一致性hash演算法確認當前客戶端對應的cacheserver的hash值以及要儲存資料key的hash進行對應,確認cacheserver,獲取connection進行資料儲存

2、memcache的get方法:通過一致性hash演算法確認當前客戶端對應的cacheserver的hash值以及要提取資料的hash值,進而確認儲存的cacheserver,獲取connection進行資料提取

五、總結

1、一致性hash演算法只是幫我們減少cache叢集中的機器數量增減的時候,cache的資料能進行最少重建。只要cache叢集的server數量有變化,必然產生資料命中的問題

2、對於資料的分佈均衡問題,通過虛擬節點的思想來達到均衡分配。當然,我們cache server節點越少就越需要虛擬節點這個方式來均衡負載。

3、我們的cache客戶端根本不會維護一個map來記錄每個key儲存在哪裡,都是通過key的hash和cacheserver(也許ip可以作為引數)的hash計算當前的key應該儲存在哪個節點上。

4、當我們的cache節點崩潰了。我們必定丟失部分cache資料,並且要根據活著的cache server和key進行新的一致性匹配計算。有可能對部分沒有丟失的資料也要做重建...

5、至於正常到達資料儲存節點,如何找到key對應的資料,那就是cache server本身的內部演算法實現了,此處不做描述。

 前言

對於之前所講的master+slave進行讀寫分離同時通過sentinel叢集保障高可用的架構,對於一般的資料量系統已經足夠。但是對於資料量龐大的T級別的資料,單master可能就無法滿足橫向擴充套件的場景。

所以redis cluster支援多master+slave架構,支援讀寫分離和主備切換,多個master支援分片hash slot分散式儲存資料

 

1、redis cluster介紹

redis cluster

(1)自動將資料進行分片,每個master上放一部分資料
(2)提供內建的高可用支援,部分master不可用時,還是可以繼續工作的

在redis cluster架構下,每個redis要放開兩個埠號,比如一個是6379,另外一個就是加10000的埠號,比如16379

16379埠號是用來進行節點間通訊的,也就是cluster bus的東西,叢集匯流排。cluster bus的通訊,用來進行故障檢測,配置更新,故障轉移授權

cluster bus用了另外一種二進位制的協議,主要用於節點間進行高效的資料交換,佔用更少的網路頻寬和處理時間

2、最老土的hash演算法和弊端(大量快取重建)

3、一致性hash演算法(自動快取遷移)+虛擬節點(自動負載均衡)

4、redis cluster的hash slot演算法

redis cluster有固定的16384個hash slot,對每個key計算CRC16值,然後對16384取模,可以獲取key對應的hash slot

redis cluster中每個master都會持有部分slot,比如有3個master,那麼可能每個master持有5000多個hash slot

hash slot讓node的增加和移除很簡單,增加一個master,就將其他master的hash slot移動部分過去,減少一個master,就將它的hash slot移動到其他master上去

移動hash slot的成本是非常低的

客戶端的api,可以對指定的資料,讓他們走同一個hash slot,通過hash tag來實現

redis cluster的重要配置

1 cluster-enabled <yes/no>
2 
3 cluster-config-file <filename>指定一個檔案,供cluster模式下的redis例項將叢集狀態儲存在起來,包括叢集中其他機器的資訊,比如節點的上線和下線,故障轉移,這些不是我們去維護,提供一個檔案地址,讓redis自己去維護
4 
5 cluster-node-timeout <milliseconds>:節點存活超時時長,超過一定時長,認為節點宕機,master宕機的話就會觸發主備切換,slave宕機就不會提供服務

 

在3臺機器上啟動6個redis例項
對於redis cluster叢集,要求至少3個master,從而能夠組成一個健壯的分散式的叢集,每個master都建議至少給一個slave,3個master,3個slave,所以建議在正式環境下,能夠部署6臺機器去搭建redis cluster叢集,最少的情況是有3臺機器,此時需要master和對應的slave不再同一臺機器
我們模擬7001-7006埠號來部署6個redis節點,每臺機器部署兩個節點:

 1 mkdir -p /etc/redis-cluster   存放cluster-config-file資訊
 2 mkdir -p /var/log/redis       存放redis的日誌資訊
 3 mkdir -p /var/redis/7001      存放redis的持久化檔案
 4 
 5 配置檔案中的改動:
 6 
 7 port 7001
 8 cluster-enabled yes  -- 啟用 cluster 叢集
 9 cluster-config-file /etc/redis-cluster/node-7001.conf
10 cluster-node-timeout 15000
11 daemonize   yes                         
12 pidfile     /var/run/redis_7001.pid                         
13 dir         /var/redis/7001     
14 logfile /var/log/redis/7001.log
15 bind 192.168.1.199      
16 appendonly yes
17 #必須要關掉 slaveof 配置

 

 

在對應的每臺機器下的/etc/init.d中,放2個對應埠號的啟動指令碼,分別為: redis_7001, redis_7002…需要注意的是每個啟動指令碼內,都一定要修改對應的埠號

我們需要安裝官方提供的redis-trib.rb來完成叢集的管理:

1 yum install -y ruby
2 yum install -y rubygems
3 gem install redis

第三步你的ruby版本太低會報錯

請跳轉 https://www.cnblogs.com/PatrickLiu/p/8454579.html 

將redis-trib.rb配置到環境變數:

cp /usr/local/redis-3.2.8/src/redis-trib.rb /usr/local/bin

執行如下命令:

redis-trib.rb create --replicas 1 192.168.1.199:7001 192.168.1.107:7002 192.168.1.104:7003 192.168.1.104:7004 192.168.1.105:7005 192.168.1.105:7006


replicas表示每個master對應的slave節點數量,後續為所有節點服務地址。成功執行後會自動將所有節點配置成叢集架構模式,會自動有以下特點:
讀寫分離、master-slave高可用主備切換、橫向master資料分片

可以通過以下命令來核查:

redis-trib.rb check 192.168.1.199:7001

 
 

之前我們所搭建的一主多從架構模式,是為來水平擴充套件,但是基於redis cluster本身的master就可以進行橫向擴充套件,所以我們使用redis cluster架構模式,讀寫都在master即可,對應的slave節點主要是進行熱備切換。並且,redis cluster預設是沒有開啟在slave節點上的讀操作,需要執行readonly命令來開啟,此外,jedis也會將請求都發送至master,需要重新封裝或者修改原始碼來達到基於redis cluster的讀寫分離實現,所以也沒有必要在redis cluster進行讀寫分離。