zoukankan      html  css  js  c++  java
  • consistent hash 算法在 memchached

    当前很多大型的web系统为了减轻数据库服务器负载,会采用memchached作为缓存系统以提高响应速度。

    目录:

    1. memchached简介
    2. hash
      • 取模
      • 一致性hash
      • 虚拟节点
      • 源码解析
    3. 参考资料

    1. memchached简介

    memcached是一个开源的高性能分布式内存对象缓存系统。
    其实思想还是比较简单的,实现包括server端(memcached开源项目一般只单指server端)和client端两部分:

    • server端本质是一个in-memory key-value store,通过在内存中维护一个大的hashmap用来存储小块的任意数据,对外通过统一的简单接口(memcached protocol)来提供操作。
    • client端是一个library,负责处理memcached protocol的网络通信细节,与memcached server通信,针对各种语言的不同实现分装了易用的API实现了与不同语言平台的集成。
    • web系统则通过client库来使用memcached进行对象缓存。

    2. hash

    memcached的分布式主要体现在client端,对于server端,仅仅是部署多个memcached server组成集群,每个server独自维护自己的数据(互相之间没有任何通信),通过daemon监听端口等待client端的请求。
    而在client端,通过一致的hash算法,将要存储的数据分布到某个特定的server上进行存储,后续读取查询使用同样的hash算法即可定位。

    client端可以采用各种hash算法来定位server:

    取模

    最简单的hash算法

    targetServer = serverList[hash(key) % serverList.size]

    直接用key的hash值(计算key的hash值的方法可以自由选择,比如算法CRC32、MD5,甚至本地hash系统,如java的hashcode)模上server总数来定位目标server。这种算法不仅简单,而且具有不错的随机分布特性。

    但是问题也很明显,server总数不能轻易变化。因为如果增加/减少memcached server的数量,对原先存储的所有key的后续查询都将定位到别的server上,导致所有的cache都不能被命中而失效。

    一致性hash

    为了解决这个问题,需要采用一致性hash算法(consistent hash)
    相对于取模的算法,一致性hash算法除了计算key的hash值外,还会计算每个server对应的hash值,然后将这些hash值映射到一个有限的值域上(比如0~2^32)。通过寻找hash值大于hash(key)的最小server作为存储该key数据的目标server。如果找不到,则直接把具有最小hash值的server作为目标server。

    为了方便理解,可以把这个有限值域理解成一个环,值顺时针递增。

    如上图所示,集群中一共有5个memcached server,已通过server的hash值分布到环中。

    如果现在有一个写入cache的请求,首先计算x=hash(key),映射到环中,然后从x顺时针查找,把找到的第一个server作为目标server来存储cache,如果超过了2^32仍然找不到,则命中第一个server。比如x的值介于A~B之间,那么命中的server节点应该是B节点

    可以看到,通过这种算法,对于同一个key,存储和后续的查询都会定位到同一个memcached server上。

    那么它是怎么解决增/删server导致的cache不能命中的问题呢?
    假设,现在增加一个server F,如下图

    此时,cache不能命中的问题仍然存在,但是只存在于B~F之间的位置(由C变成了F),其他位置(包括F~C)的cache的命中不受影响(删除server的情况类似)。尽管仍然有cache不能命中的存在,但是相对于取模的方式已经大幅减少了不能命中的cache数量。

    虚拟节点
    但是,这种算法相对于取模方式也有一个缺陷:当server数量很少时,很可能他们在环中的分布不是特别均匀,进而导致cache不能均匀分布到所有的server上。

    如图,一共有3台server – A,B,C。命中B的几率远远高于A和C。
    为解决这个问题,需要使用虚拟节点的思想:为每个物理节点(server)在环上分配100~200个点,这样环上的节点较多,就能抑制分布不均匀。
    当为cache定位目标server时,如果定位到虚拟节点上,就表示cache真正的存储位置是在该虚拟节点代表的实际物理server上。

    另外,如果每个实际server的负载能力不同,可以赋予不同的权重,根据权重分配不同数量的虚拟节点。

    源码解析:

    下面结合一个java的memcached client(gwhalin / Memcached-Java-Client)的源码来看一下consistent hash的实现。
    首先看server的分布:

    // 采用有序map来模拟环
    this.consistentBuckets = new TreeMap();
    
    MessageDigest md5 = MD5.get();//用MD5来计算key和server的hash值
    
    // 计算总权重
    if ( this.totalWeight 	for ( int i = 0; i < this.weights.length; i++ )
    		this.totalWeight += ( this.weights[i] == null ) ? 1 : this.weights[i];
    } else if ( this.weights == null ) {
    	this.totalWeight = this.servers.length;
    }
    
    // 为每个server分配虚拟节点
    for ( int i = 0; i < servers.length; i++ ) {
    	// 计算当前server的权重
    	int thisWeight = 1;
    	if ( this.weights != null && this.weights[i] != null )
    		thisWeight = this.weights[i];
    
    	// factor用来控制每个server分配的虚拟节点数量
    	// 权重都相同时,factor=40
    	// 权重不同时,factor=40*server总数*该server权重所占的百分比
    	// 总的来说,权重越大,factor越大,可以分配越多的虚拟节点
    	double factor = Math.floor( ((double)(40 * this.servers.length * thisWeight)) / (double)this.totalWeight );
    
    	for ( long j = 0; j < factor; j++ ) {
    		// 每个server有factor个hash值
    		// 使用server的域名或IP加上编号来计算hash值
    		// 比如server - "172.45.155.25:11111"就有factor个数据用来生成hash值:
    		// 172.45.155.25:11111-1, 172.45.155.25:11111-2, ..., 172.45.155.25:11111-factor
    		byte[] d = md5.digest( ( servers[i] + "-" + j ).getBytes() );
    
    		// 每个hash值生成4个虚拟节点
    		for ( int h = 0 ; h < 4; h++ ) {
    			Long k =
    				((long)(d[3+h*4]&0xFF) << 24)
    			      | ((long)(d[2+h*4]&0xFF) << 16)
    			      | ((long)(d[1+h*4]&0xFF) << 8 )
    			      | ((long)(d[0+h*4]&0xFF));
    
    			// 在环上保存节点
    			consistentBuckets.put( k, servers[i] );
    		}
    
    	}
    	// 每个server一共分配4*factor个虚拟节点
    }

    每个server根据权重获得一个虚拟节点数量控制因子factor,然后由services[i]+”-”+i来生成factor个hash值,生成hash值时采用MD5算法。
    由于MD5长度是16个字节,正好划分成4段,每段4字节,这样每段就对应一个虚拟节点。linex-liney通过把这一段的4字节拼装成连续的32bit,作为低32位拉升为一个Long。

    为key定位cache存储的server:

    // 用MD5来计算key的hash值
    MessageDigest md5 = MD5.get();
    md5.reset();
    md5.update( key.getBytes() );
    byte[] bKey = md5.digest();
    
    // 取MD5值的低32位作为key的hash值
    long hv = ((long)(bKey[3]&0xFF) << 24) | ((long)(bKey[2]&0xFF) << 16) | ((long)(bKey[1]&0xFF) << 8 ) | (long)(bKey[0]&0xFF);
    
    // hv的tailMap的第一个虚拟节点对应的即是目标server
    SortedMap tmap = this.consistentBuckets.tailMap( hv );
    return ( tmap.isEmpty() ) ? this.consistentBuckets.firstKey() : tmap.firstKey();

    3. 参考资料

    1. 首次提出consistent hash的论文 – “Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web”
    2. Consistent hashing Wiki – http://en.wikipedia.org/wiki/Consistent_hashing
    3. Ketama: Consistent Hashing
    4. memcached开源项目主页
    5. memcached google code主页
    6. gwhalin / Memcached-Java-Client主页

    from:http://www.slimeden.com/2011/09/web/memcached_client_hash

  • 相关阅读:
    不常用的cmd命令
    js获取宽度
    Marshaling Data with Platform Invoke 概览
    Calling a DLL Function 之三 How to: Implement Callback Functions
    Marshaling Data with Platform Invoke 之四 Marshaling Arrays of Types
    Marshaling Data with Platform Invoke 之一 Platform Invoke Data Types
    Marshaling Data with Platform Invoke 之三 Marshaling Classes, Structures, and Unions(用时查阅)
    Calling a DLL Function 之二 Callback Functions
    WCF 引论
    Marshaling Data with Platform Invoke 之二 Marshaling Strings (用时查阅)
  • 原文地址:https://www.cnblogs.com/dkblog/p/2398332.html
Copyright © 2011-2022 走看看