【转】一致性hash算法与server列表维护

标签: 一致性 hash 算法 | 发表时间:2016-02-05 18:27 | 作者:IXHONG
出处:http://www.iteye.com

  考虑到不用重复造轮子,特此转载好文,出处http://shift-alt-ctrl.iteye.com/blog/1963244

    普通的hash算法有个很大的问题:当hash的"模数"发生变化时,整个hash数据结构就需要重新hash,重新hash之后的数据分布一定会和hash之前的不同;在很多场景下,"模数"的变化时必然的,但是这种"数据分布"的巨大变化却会带来一些麻烦.所以,就有了"一致性hash",当然学术界对"一致性hash"的阐述,还远远不止这些.

    在编程应用方面,最直观的例子就是"分布式缓存",一个cache集群中,有N台物理server,为了提升单台server的支撑能力,可能会考虑将数据通过hash的方式相对均匀的分布在每个server上.

    判定方式: location = hashcode(key) % N;事实上,由于需要,N可能会被增加或者削减,不管程序上是否能够妥善的支持N的变更,单从"数据迁移"的面积而言,也是非常大的.

    一致性Hash很巧妙的简化了这个问题,同时再使用"虚拟节点"的方式来细分数据的分布.



 

F1

    图示中表名,有2个物理server节点,每个物理server节点有多个Snode虚拟节点,server1和server2的虚拟节点互相"穿插"且依次排列,每个snode都有一个code,它表示接受数据的hashcode起始值(或终止值),比如上述图示中第一个snode.code为500,即当一个数据的hashcode值在[0,500]时则会被存储在它上.

    引入虚拟节点之后,事情就会好很多;假如KEY1分布在Snode3上,snode3事实为物理server1,当server1故障后,snode2也将被移除,那么KEY1将会被分布在"临近的"虚拟节点上--snode2(或者snode4,由实现而定);无论是存取,下一次对KEY1的操作将会有snode2(或snode4)来服务.

    1) 一个物理server在逻辑上分成N个虚拟节点(本例中为256个)

    2) 多个物理server的虚拟节点需要散列分布,即互相"穿插".

    3) 所有的虚拟节点,在逻辑上形成一个链表

    4) 每个虚拟节点,负责一定区间的hashcode值.

import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;


public class ServersPool {

    private static final int VIRTUAL_NODES_NUMBER = 256;//物理节点对应的虚拟节点的个数
    private static final String TAG = ".-vtag-.";
    private NavigableMap<Long, SNode> innerPool = new TreeMap<Long, SNode>();
    private Hashing hashing = new Hashing();

    /**
     * 新增物理server地址
     * @param address
     * @param  weight
     * 权重,权重越高,其虚拟节点的个数越高,事实上没命中的概率越高
     * @throws Exception
     */
    public synchronized void addServer(String address,int weight) throws Exception {
        SNode prev = null;
        SNode header = null;
        String[] tmp = address.split(":");
        InnerServer server = new InnerServer(tmp[0], Integer.parseInt(tmp[1]));
        server.init();
        //将一个address下的所有虚拟节点SNode形成链表,可以在removeServer,以及
        //特殊场景下使用
        int max = 1;
        if(weight > 0){
            max = VIRTUAL_NODES_NUMBER * weight;
        }
        for (int i = 0; i < max; i++) {
            long code = hashing.hash(address + TAG + i);
            SNode current = new SNode(server, code);
            if (header == null) {
                header = current;
            }
            current.setPrev(prev);
            innerPool.put(code, current);
            prev = current;
        }
    }
    /**
     * 删除物理server地址,伴随着虚拟节点的删除
     * @param address
     */
    public synchronized void removeServer(String address) {
        long code = hashing.hash(address + TAG + (VIRTUAL_NODES_NUMBER - 1));
        SNode current = innerPool.get(code);
        if(current == null){
            return;
        }
        if(!current.getAddress().equalsIgnoreCase(address)){
            return;
        }
        current.getServer().close();;
        while (current != null) {
            current = innerPool.remove(current.getCode()).getPrev();
        }

    }

    /**
     * 根据指定的key,获取此key应该命中的物理server信息
     * @param key
     * @return
     */
    public InnerServer getServer(String key) {
        long code = hashing.hash(key);
        SNode snode = innerPool.lowerEntry(code).getValue();
        if (snode == null) {
            snode = innerPool.firstEntry().getValue();
        }
        return snode.getServer();
    }


    /**
     * 虚拟节点描述
     */
    class SNode {
        Long code;
        InnerServer server;
        SNode prev;

        SNode(InnerServer server, Long code) {
            this.server = server;
            this.code = code;
        }

        SNode getPrev() {
            return prev;
        }

        void setPrev(SNode prev) {
            this.prev = prev;
        }

        Long getCode() {
            return this.code;
        }

        InnerServer getServer() {
            return server;
        }
        String getAddress(){
            return server.ip + ":" + server.port;
        }
    }

    /**
     * hashcode生成
     */
    class Hashing {
        //少量优化性能
        private ThreadLocal<MessageDigest> md5Holder = new ThreadLocal<MessageDigest>();
        private Charset DEFAULT_CHARSET = Charset.forName("utf-8");

        public long hash(String key) {
            return hash(key.getBytes(DEFAULT_CHARSET));
        }

        public long hash(byte[] key) {
            try {
                if (md5Holder.get() == null) {
                    md5Holder.set(MessageDigest.getInstance("MD5"));
                }
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("no md5 algorythm found");
            }
            MessageDigest md5 = md5Holder.get();

            md5.reset();
            md5.update(key);
            byte[] bKey = md5.digest();
            long res = ((long) (bKey[3] & 0xFF) << 24)
                    | ((long) (bKey[2] & 0xFF) << 16)
                    | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF);
            return res;
        }
    }

    /**
     * 与物理server的TCP链接,用于实际的IO操作
     */
    class InnerServer {
        String ip;
        int port;
        Socket socket;

        InnerServer(String ip, int port) {
            this.ip = ip;
            this.port = port;
        }

        synchronized void init() throws Exception {
            SocketAddress socketAddress = new InetSocketAddress(ip, port);
            socket = new Socket();
            socket.connect(socketAddress, 30000);
        }

        public boolean write(byte[] sources) {
            //TODO
            return true;
        }

        public byte[] read() {
            //TODO
            return new byte[]{};
        }

        public void close(){
             if(socket == null || socket.isClosed()){
                 return;
             }
            try{
                socket.close();
            } catch (Exception e){
                //
            }
        }
    }
}

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [一致性 hash 算法] 推荐:

一致性HASH算法

- - 企业架构 - ITeye博客
一致性 hash 算法( consistent hashing ). consistent hashing 算法早在 1997 年就在论文 . Consistent hashing and random trees 中被提出,目前在cache 系统中应用越来越广泛;. 比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object 映射到 N 个 cache 上呢,你很可能会采用类似下面的通用方法计算 object 的 hash 值,然后均匀的映射到到 N 个 cache ;.

一致性Hash算法背景(转)

- - 开源软件 - ITeye博客
一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似. 一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用.   但现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性hash时采用如下步骤:.

一致性hash算法测试

- - Java - 编程语言 - ITeye博客
package com.xll; //服务器对象 public class Server {. private void init() { // 初始化一致性hash环. for (int i = 0; i != servers.size(); ++i) { // 每个真实机器节点都需要关联虚拟节点.

一致性hash

- - 互联网 - ITeye博客
一致性hash算法 - consistent hashing. 分类:  算法艺术2010-02-02 09:19 69836人阅读  评论(97)  收藏  举报. 算法 cache object 服务器 存储 c. 一致性 hash 算法( consistent hashing ).

[转][转]memcache的一致性hash算法使用

- - heiyeluren的Blog
来源: http://blog.csdn.net/kongqz/article/details/6695417.   1、我们的memcache客户端(这里我看的spymemcache的源码),使用了一致性hash算法ketama进行数据存储节点的选择. 与常规的hash算法思路不同,只是对我们要存储数据的key进行hash计算,分配到不同节点存储.

一致性hash算法在memcached中的使用

- - CSDN博客推荐文章
  1、我们的memcache客户端(这里我看的spymemcache的源码),使用了一致性hash算法ketama进行数据存储节点的选择. 与常规的hash算法思路不同,只是对我们要存储数据的key进行hash计算,分配到不同节点存储. 一致性hash算法是对我们要存储数据的服务器进行hash计算,进而确认每个key的存储位置.

【转】一致性hash算法与server列表维护

- - 编程语言 - ITeye博客
考虑到不用重复造轮子,特此转载好文,出处http://shift-alt-ctrl.iteye.com/blog/1963244.     普通的hash算法有个很大的问题:当hash的"模数"发生变化时,整个hash数据结构就需要重新hash,重新hash之后的数据分布一定会和hash之前的不同;在很多场景下,"模数"的变化时必然的,但是这种"数据分布"的巨大变化却会带来一些麻烦.所以,就有了"一致性hash",当然学术界对"一致性hash"的阐述,还远远不止这些..

Hash算法的使用

- khsing - Glider&#39;s home
在对语料文本进行2,3元切分时,需要借助hash表来获得切分内容在内存中的位置,以便能够记录语料库中出现的次数. 以前知道有很多hash算法,但没认真研究过,今天才知道hash算法差距还是很明显的. 首先我选择的是暴雪在魔兽里的hash算法,这个算法很高级,是time33类型的一个变种(有关time33的介绍,可以参考:http://www.cnblogs.com/napoleon_liu/articles/1911571.html),而且很巧妙的借助3次hash避免字符串比较这种费时的操作,并且不用链表来存储冲突,3次hash冲突值能相等的可能行只有1/10的23次方,应该说不可能冲突了.

memcached的总结和分布式一致性hash

- - 开源软件 - ITeye博客
当前很多大型的web系统为了减轻数据库服务器负载,会采用memchached作为缓存系统以提高响应速度. memcached是一个开源的高性能分布式内存对象缓存系统. 其实思想还是比较简单的,实现包括server端(memcached开源项目一般只单指server端)和client端两部分:. server端本质是一个in-memory key-value store,通过在内存中维护一个大的hashmap用来存储小块的任意数据,对外通过统一的简单接口(memcached protocol)来提供操作.

从头到尾彻底解析Hash表算法

- - 博客园_知识库
  作者:July、wuliming、pkuoliver.   说明:本文分为三部分内容, 第一部分为一道百度面试题Top K算法的详解;第二部分为关于Hash表算法的详细阐述;第三部分为打造一个最快的Hash表算法.    第一部分:Top K 算法详解.   问题描述(百度面试题):.   搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节.