<分布式寻址>一致性hash算法

一致性Hash算法背景

一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似。一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用。

原理

基本概念

一致性哈希算法(Consistent Hashing)最早在论文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。简单来说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希空间环如下:


整个空间按顺时针方向组织。0和232-1在零点中方向重合。


在集群服务器确定以后,将各个服务器使用Hash函数进行一个哈希计算,哈希计算可以选择服务器的ip地址或者主机名等关键字进行哈希,这样就完成了节点在hash环上的位置分配,假设集群有四台服务器,最终效果如下:


服务器分配

接下来,每台服务器在hash环上的位置分配完成后,在客户端对缓存key进行同样函数的hash运算,得出hash值,同样得到环上的一个位置,从这个位置顺时针找到最近的一个服务器节点,比如遍历所有节点位置和key位置差值取最小值,这样就完成了路由。

例如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:


负载均衡

根据一致性哈希算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。


容错性和拓展性

上篇博客提到了简单哈希的缺点,当服务器宕机、扩容、缩容时,容错性和扩展性差,那么一致性hash的容错性和拓展性如何呢?

接着上图讨论,现假设Node C不幸宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性哈希算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。

下面考虑另外一种情况,如果在系统中增加一台服务器Node X,如下图所示:


扩容情况

此时对象Object A、B、D不受影响,只有对象C需要重定位到新的Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。

综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。

数据倾斜问题

一致性hash算法的缺陷是它无法控制节点分布的均匀性,因为hash的结果并不一定均匀分布在环上对称的位置,极端的情况,举个例子,现在有A、B、C三个服务器节点,hash的结果紧挨在一起,那么根据一致性hash,一定是极少量的key会访问到从0点开始顺时针数到的第二、三个节点,绝大部分key都会访问到顺时针数到的第一个节点。如下图:


不均匀分布

hash(key)运算结果落在A和B之间的请求会访问到B服务器,hash(key)运算结果落在B和C之间的请求会访问到C服务器,而hash(key)落在A和C之间的请求会访问到A服务器。这样A的压力不言而喻。

解决方案

虚拟节点

为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制

简单来说就是将一台服务器加上编号尾缀进行哈希,每台服务器就会有多个结果


虚拟节点

同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。

良好的分布式cahce系统中的一致性hash算法应该满足以下几个方面:

平衡性(Balance)

平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。

单调性(Monotonicity)

单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲区加入到系统中,那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去,而不会被映射到旧的缓冲集合中的其他缓冲区。简单的哈希算法往往不能满足单调性的要求,如最简单的线性哈希:x = (ax + b) mod (P),在上式中,P表示全部缓冲的大小。不难看出,当缓冲大小发生变化时(从P1到P2),原来所有的哈希结果均会发生变化,从而不满足单调性的要求。哈希结果的变化意味着当缓冲空间发生变化时,所有的映射关系需要在系统内全部更新。而在P2P系统内,缓冲的变化等价于Peer加入或退出系统,这一情况在P2P系统中会频繁发生,因此会带来极大计算和传输负荷。单调性就是要求哈希算法能够应对这种情况。

分散性(Spread)

在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。

负载(Load)

负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

平滑性(Smoothness)

平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的。


一致性hash代码实现:

package com.ctrip.dcs;

import java.io.UnsupportedEncodingException;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.util.*;

public class ConsistencyHash {

    private TreeMap<Long,Object> nodes = null;

    //真实服务器节点信息

    private List<Object> shards = new ArrayList();

    //设置虚拟节点数目

    private int VIRTUAL_NUM = 4;

    /**

    * 初始化一致环

    */

    public void init() {

        shards.add("192.168.0.0-服务器0");

        shards.add("192.168.0.1-服务器1");

        shards.add("192.168.0.2-服务器2");

        shards.add("192.168.0.3-服务器3");

        shards.add("192.168.0.4-服务器4");

        nodes = new TreeMap<>();

        for(int i=0; i<shards.size(); i++) {

            Object shardInfo = shards.get(i);

            for(int j=0; j<VIRTUAL_NUM; j++) {

                nodes.put(hash(computeMd5("SHARD-" + i + "-NODE-" + j),j), shardInfo);

            }

        }

    }

    /**

    * 根据key的hash值取得服务器节点信息

    * @param hash

    * @return

    */

    public Object getShardInfo(long hash) {

        Long key = hash;

        SortedMap<Long, Object> tailMap=nodes.tailMap(key);

        if(tailMap.isEmpty()) {

            key = nodes.firstKey();

        } else {

            key = tailMap.firstKey();

        }

        return nodes.get(key);

    }

    /**

    * 打印圆环节点数据

    */

    public void printMap() {

        System.out.println(nodes);

    }

    /**

    * 根据2^32把节点分布到圆环上面。

    * @param digest

    * @param nTime

    * @return

    */

    public long hash(byte[] digest, int nTime) {

        long rv = ((long) (digest[3+nTime*4] & 0xFF) << 24)

                | ((long) (digest[2+nTime*4] & 0xFF) << 16)

                | ((long) (digest[1+nTime*4] & 0xFF) << 8)

                | (digest[0+nTime*4] & 0xFF);

        return rv & 0xffffffffL; /* Truncate to 32-bits */

    }

    /**

    * Get the md5 of the given key.

    * 计算MD5值

    */

    public byte[] computeMd5(String k) {

        MessageDigest md5;

        try {

            md5 = MessageDigest.getInstance("MD5");

        } catch (NoSuchAlgorithmException e) {

            throw new RuntimeException("MD5 not supported", e);

        }

        md5.reset();

        byte[] keyBytes = null;

        try {

            keyBytes = k.getBytes("UTF-8");

        } catch (UnsupportedEncodingException e) {

            throw new RuntimeException("Unknown string :" + k, e);

        }

        md5.update(keyBytes);

        return md5.digest();

    }

    public static void main(String[] args) {

        Random ran = new Random();

        ConsistencyHash hash = new ConsistencyHash();

        hash.init();

        hash.printMap();

        //循环50次,是为了取50个数来测试效果,当然也可以用其他任何的数据来测试

        for(int i=0; i<50; i++) {

            System.out.println(hash.getShardInfo(hash.hash(hash.computeMd5(String.valueOf(i)),ran.nextInt(hash.VIRTUAL_NUM))));

        }

    }

}


下篇博客介绍一下redis cluster的hash slot算法,介绍一下redis分片技术。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容