redis入門(mén)-一致性哈希算法

一莲祸、前言

這次我們來(lái)討論下redis cluster依賴(lài)的一個(gè)核心的算法赂韵,一致性哈希算法。我們都知道税产,redis cluster能夠做到動(dòng)態(tài)地?cái)U(kuò)容,在擴(kuò)容的過(guò)程中如何保證數(shù)據(jù)的遷移盡可能地型当馈辟拷?
如果采用傳統(tǒng)的hash(object)%N算法,那么在有機(jī)器添加或者刪除后阐斜,幾乎所有數(shù)據(jù)都要做一次大的遷移衫冻,這是我們不希望看到的結(jié)果,那么一致性哈希是如何解決這個(gè)問(wèn)題的谒出。

二羽杰、標(biāo)準(zhǔn)

一致性hash算法提出了在動(dòng)態(tài)變化的Cache環(huán)境中渡紫,判定哈希算法好壞的四個(gè)定義:

1.平衡性(Balance):平衡性是指哈希的結(jié)果能夠盡可能分布到所有的緩沖中去,這樣可以使得所有的緩沖空間都得到利用考赛。很多哈希算法都能夠滿足這一條件。

2.單調(diào)性(Monotonicity):?jiǎn)握{(diào)性是指如果已經(jīng)有一些內(nèi)容通過(guò)哈希分派到了相應(yīng)的緩沖中莉测,又有新的緩沖加入到系統(tǒng)中颜骤。哈希的結(jié)果應(yīng)能夠保證原有已分配的內(nèi)容可以被映射到原有的或者新的緩沖中去,而不會(huì)被映射到舊的緩沖集合中的其他緩沖區(qū)捣卤。

3.分散性(Spread):在分布式環(huán)境中忍抽,終端有可能看不到所有的緩沖,而是只能看到其中的一部分董朝。當(dāng)終端希望通過(guò)哈希過(guò)程將內(nèi)容映射到緩沖上時(shí)鸠项,由于不同終端所見(jiàn)的緩沖范圍有可能不同,從而導(dǎo)致哈希的結(jié)果不一致子姜,最終的結(jié)果是相同的內(nèi)容被不同的終端映射到不同的緩沖區(qū)中祟绊。這種情況顯然是應(yīng)該避免的,因?yàn)樗鼘?dǎo)致相同內(nèi)容被存儲(chǔ)到不同緩沖中去哥捕,降低了系統(tǒng)存儲(chǔ)的效率牧抽。分散性的定義就是上述情況發(fā)生的嚴(yán)重程度。好的哈希算法應(yīng)能夠盡量避免不一致的情況發(fā)生遥赚,也就是盡量降低分散性扬舒。

4.負(fù)載(Load):負(fù)載問(wèn)題實(shí)際上是從另一個(gè)角度看待分散性問(wèn)題。既然不同的終端可能將相同的內(nèi)容映射到不同的緩沖區(qū)中凫佛,那么對(duì)于一個(gè)特定的緩沖區(qū)而言讲坎,也可能被不同的用戶映射為不同 的內(nèi)容。與分散性一樣愧薛,這種情況也是應(yīng)當(dāng)避免的晨炕,因此好的哈希算法應(yīng)能夠盡量降低緩沖的負(fù)荷。

三厚满、圖解
一致性哈希圖解
這篇文章介紹得很清楚府瞄,所以這里就不再重復(fù)了。這里只提一點(diǎn):
虛擬結(jié)點(diǎn)的作用:
物理結(jié)點(diǎn)普遍的數(shù)量不會(huì)很多碘箍,如果直接以物理結(jié)點(diǎn)作為hash遵馆,很有可能數(shù)據(jù)分布會(huì)很不均勻,因此這里引入虛擬結(jié)點(diǎn)希望把結(jié)點(diǎn)的數(shù)據(jù)增加丰榴,最終達(dá)到一個(gè)盡可能平衡的狀態(tài)货邓。

  • 那么實(shí)際redis的虛擬結(jié)點(diǎn)復(fù)制數(shù)量是多少?如何定義一個(gè)合理的復(fù)制數(shù)量
  • 新增結(jié)點(diǎn)的時(shí)候如何把這個(gè)新的結(jié)點(diǎn)信息同步到各個(gè)客戶端四濒?這里又會(huì)涉及到數(shù)據(jù)同步的問(wèn)題换况,客戶端同步到新結(jié)點(diǎn)的數(shù)據(jù)职辨,但是redis的key還沒(méi)進(jìn)行遷移,怎么辦戈二?

四舒裤、源碼

HashFunction.java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/*
 * 實(shí)現(xiàn)一致性哈希算法中使用的哈希函數(shù),使用MD5算法來(lái)保證一致性哈希的平衡性
 */
public class HashFunction {
    private MessageDigest md5 = null;

    public long hash(String key) {
        if (md5 == null) {
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("no md5 algrithm found");
            }
        }

        md5.reset();
        md5.update(key.getBytes());
        byte[] bKey = md5.digest();
        //具體的哈希函數(shù)實(shí)現(xiàn)細(xì)節(jié)--每個(gè)字節(jié) & 0xFF 再移位
        long result = ((long) (bKey[3] & 0xFF) << 24)
                | ((long) (bKey[2] & 0xFF) << 16
                | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF));
        return result & 0xffffffffL;
    }
}

ConsistentHash.java

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

public class ConsistentHash<T> {
    private final HashFunction hashFunction;
    private final int numberOfReplicas;// 節(jié)點(diǎn)的復(fù)制因子,實(shí)際節(jié)點(diǎn)個(gè)數(shù) * numberOfReplicas =
    // 虛擬節(jié)點(diǎn)個(gè)數(shù)
    private final SortedMap<Long, T> circle = new TreeMap<Long, T>();// 存儲(chǔ)虛擬節(jié)點(diǎn)的hash值到真實(shí)節(jié)點(diǎn)的映射

    public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
                          Collection<T> nodes) {
        this.hashFunction = hashFunction;
        this.numberOfReplicas = numberOfReplicas;
        for (T node : nodes)
            add(node);
    }

    public void add(T node) {
        for (int i = 0; i < numberOfReplicas; i++)
            // 對(duì)于一個(gè)實(shí)際機(jī)器節(jié)點(diǎn) node, 對(duì)應(yīng) numberOfReplicas 個(gè)虛擬節(jié)點(diǎn)
            /*
             * 不同的虛擬節(jié)點(diǎn)(i不同)有不同的hash值,但都對(duì)應(yīng)同一個(gè)實(shí)際機(jī)器node
             * 虛擬node一般是均衡分布在環(huán)上的,數(shù)據(jù)存儲(chǔ)在順時(shí)針?lè)较虻奶摂Mnode上
             */
            circle.put(hashFunction.hash(node.toString() + i), node);
    }

    public void remove(T node) {
        for (int i = 0; i < numberOfReplicas; i++)
            circle.remove(hashFunction.hash(node.toString() + i));
    }

    /*
     * 獲得一個(gè)最近的順時(shí)針節(jié)點(diǎn),根據(jù)給定的key 取Hash
     * 然后再取得順時(shí)針?lè)较蛏献罱囊粋€(gè)虛擬節(jié)點(diǎn)對(duì)應(yīng)的實(shí)際節(jié)點(diǎn)
     * 再?gòu)膶?shí)際節(jié)點(diǎn)中取得 數(shù)據(jù)
     */
    public T get(Object key) {
        if (circle.isEmpty())
            return null;
        long hash = hashFunction.hash((String) key);// node 用String來(lái)表示,獲得node在哈希環(huán)中的hashCode
        if (!circle.containsKey(hash)) {//數(shù)據(jù)映射在兩臺(tái)虛擬機(jī)器所在環(huán)之間,就需要按順時(shí)針?lè)较驅(qū)ふ覚C(jī)器
            SortedMap<Long, T> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }

    public long getSize() {
        return circle.size();
    }

    /*
     * 查看MD5算法生成的hashCode值---表示整個(gè)哈希環(huán)中各個(gè)虛擬節(jié)點(diǎn)位置
     */
    public void testBalance(){
        Set<Long> sets  = circle.keySet();//獲得TreeMap中所有的Key
        SortedSet<Long> sortedSets= new TreeSet<Long>(sets);//將獲得的Key集合排序
        for(Long hashCode : sortedSets){
            System.out.println(hashCode);
        }

        System.out.println("----each location 's distance are follows: ----");
        /*
         * 查看用MD5算法生成的long hashCode 相鄰兩個(gè)hashCode的差值
         */
        Iterator<Long> it = sortedSets.iterator();
        Iterator<Long> it2 = sortedSets.iterator();
        if(it2.hasNext())
            it2.next();
        long keyPre, keyAfter;
        while(it.hasNext() && it2.hasNext()){
            keyPre = it.next();
            keyAfter = it2.next();
            System.out.println(keyAfter - keyPre);
        }
    }

    public static void main(String[] args) {
        Set<String> nodes = new HashSet<String>();
        nodes.add("A");
        nodes.add("B");
        nodes.add("C");

        ConsistentHash<String> consistentHash = new ConsistentHash<String>(new HashFunction(), 10, nodes);
        consistentHash.add("D");

        System.out.println("hash circle size: " + consistentHash.getSize());
        System.out.println("location of each node are follows: ");
        consistentHash.testBalance();

        //刪除結(jié)點(diǎn),rehash
        consistentHash.remove("D");
        consistentHash.testBalance();
    }

}

邏輯很簡(jiǎn)單觉吭,核心關(guān)注幾點(diǎn):

  • hash算法的實(shí)現(xiàn)
  • 虛擬結(jié)點(diǎn)的數(shù)量如何維護(hù)
    circle
  • 一個(gè)key如何定義存放在哪個(gè)虛擬結(jié)點(diǎn)腾供?
    public T get(Object key),取順時(shí)針最近的結(jié)點(diǎn)
  • 虛擬結(jié)點(diǎn)和物理結(jié)點(diǎn)如何映射鲜滩?
    circle.put(hashFunction.hash(node.toString() + i), node);其中i是第i個(gè)虛擬結(jié)點(diǎn)

五伴鳖、總結(jié)

后面把redis的核心源碼撈出來(lái)好好讀讀,盡量對(duì)redis和redis cluster有一個(gè)更深刻的認(rèn)識(shí)徙硅。

參考文獻(xiàn)

源碼分析
https://www.cnblogs.com/hapjin/p/4737207.html
圖解
https://blog.csdn.net/cywosp/article/details/23397179

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末榜聂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嗓蘑,更是在濱河造成了極大的恐慌须肆,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脐往,死亡現(xiàn)場(chǎng)離奇詭異休吠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)业簿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)瘤礁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人梅尤,你說(shuō)我怎么就攤上這事柜思。” “怎么了巷燥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵赡盘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缰揪,道長(zhǎng)陨享,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任钝腺,我火速辦了婚禮抛姑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艳狐。我一直安慰自己定硝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布毫目。 她就那樣靜靜地躺著蔬啡,像睡著了一般诲侮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箱蟆,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天沟绪,我揣著相機(jī)與錄音,去河邊找鬼顽腾。 笑死近零,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抄肖。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼窖杀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼漓摩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起入客,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤管毙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后桌硫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夭咬,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年铆隘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卓舵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膀钠,死狀恐怖掏湾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肿嘲,我是刑警寧澤融击,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站雳窟,受9級(jí)特大地震影響尊浪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜封救,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一拇涤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兴泥,春花似錦工育、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嘱朽。三九已至,卻和暖如春怔接,著一層夾襖步出監(jiān)牢的瞬間搪泳,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工扼脐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岸军,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓瓦侮,卻偏偏與公主長(zhǎng)得像艰赞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肚吏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351