风也温柔

计算机科学知识库

一致性hash算法 java 简单hash算法和一致性hash算法

  hash算法常见的场景有:作为负载均衡策略、分布式缓存分区、数据库分表分库等,下面将两种常见的算法做一简单的介绍及实现,(下面部分内容参考其他网站,记录在此实为当做个人笔记)

  简单hash算法

  简单hash算法在实现上为:目标 = hash(key)/ 质数

  此方法简单java代码实现如下:

   public static void main(String[] args) {

            ArrayList data = new ArrayList();
    for(int i=0;i{
    long serv = new HashServiceImpl().hash(e)%5;
                 System.out.println(serv);
    <p>![一致性hash算法 java_java hash表分配算法_hashcode hash算法][1]

                 target.compute(Math.abs(serv)+"", (k,v)->{
    if(v==null){
    return new AtomicLong();
                     }else{
                         v.getAndIncrement();
    return v;
                     }
                 });
             });
             //打印分组结果
             System.out.println(target.toString()); 
        }
    /** 
        * MurMurHash算法,是非加密HASH算法,性能很高,碰撞率低 
        */  
    
       @Override  
    public Long hash(String key) {  
           ByteBuffer buf = ByteBuffer.wrap(key.getBytes());  
    int seed = 0x1234ABCD;  
           ByteOrder byteOrder = buf.order();  
           buf.order(ByteOrder.LITTLE_ENDIAN);  
    long m = 0xc6a4a7935bd1e995L;  
    int r = 47;  
    long h = seed ^ (buf.remaining() * m);  
    long k;  
    while (buf.remaining() >= 8) {  
               k = buf.getLong();  
    
               k *= m;  
               k ^= k >>> r;  
               k *= m;  
               h ^= k;  
               h *= m;  
           }  
    if (buf.remaining() > 0) {  
               ByteBuffer finish = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);            
               finish.put(buf).rewind();  
               h ^= finish.getLong();  
               h *= m;  
           }  
           h ^= h >>> r;  
           h *= m;  
    &emsp;&emsp;![一致性hash算法 java_hashcode hash算法_java hash表分配算法][2]

           h ^= h >>> r;  
           buf.order(byteOrder);  
    return h;  

</p>
  运行结果如下,可以看到hash的结果与取模的质数数量一致。

  {0=24, 1=15, 2=8, 3=22, 4=126}

  简单Hash引起的问题

  简单hah算法计算的值会依赖于质数。如果再加入一个分区则之前的hash映射都会失效,无法动态调整。解决此问题的办法为使用一致性hash,一致性hash可以解决大部分hash引用失效的问题。在开始前先了解一下一致性hash算法的原理及相关的特性。

  1、一致性hash算法原理

  一致性哈希算法是分布式系统中常用的算法,比如有N台缓存服务器,你需要将数据缓存到这N台服务器上。一致性哈希算法可以将数据尽可能平均的存储到N台缓存服务器上,提高系统的负载均衡,并且当有缓存服务器加入或退出集群时,尽可能少的影响现有缓存服务器的命中率,减少数据对后台服务的大量冲击。

  一致性哈希算法的基本原理,把数据通过hash函数映射到一个很大的环形空间里,如下图所示:

  java hash表分配算法_hashcode hash算法_一致性hash算法 java

  A、B、C、D 4台缓存服务器通过hash函数映射环形空间上,数据的存储时,先得到一个hash值,对应到这个环中的每个位置,如缓存数据:K1对应到了图中所示的位置,然后沿顺时针找到一个机器节点A,将K1存储到A节点上,K2存储到A节点上,K3、K4存储到B节点上。

  如果B节点宕机了,则B上的数据就会落到C节点上,如下图所示:

  java hash表分配算法_一致性hash算法 java_hashcode hash算法

  这样,只会影响C节点一致性hash算法 java,对其他的节点A,D的数据不会造成影响。然而,这又会造成一个“雪崩”的情况,即C节点由于承担了B节点的数据,所以C节点的负载会变高,C节点很容易也宕机,这样依次下去,这样造成整个集群都挂了。

  hashcode hash算法_java hash表分配算法_一致性hash算法 java

  为此,引入了“虚拟节点”的概念:即把想象在这个环上有很多“虚拟节点”,数据的存储是沿着环的顺时针方向找一个虚拟节点,每个虚拟节点都会关联到一个真实节点,如下图所使用:

  java hash表分配算法_一致性hash算法 java_hashcode hash算法

  引入“虚拟节点”后,映射关系就从 {对象 ->节点 }转换到了 {对象 ->虚拟节点 }。图中的A1、A2、B1、B2、C1、C2、D1、D2都是虚拟节点,机器A负载存储A1、A2的数据,机器B负载存储B1、B2的数据,机器C负载存储C1、C2的数据。由于这些虚拟节点数量很多,均匀分布,提高了平衡性,因此不会造成“雪崩”现象。

  2、一致性hash的特性

  考虑到分布式系统每个节点都有可能失效,并且新的节点很可能动态的增加进来,如何保证当系统的节点数目发生变化时仍然能够对外提供良好的服务,这是值得考虑的,尤其实在设计分布式缓存系统时,如果某台服务器失效,对于整个系统来说如果不采用合适的算法来保证一致性,那么缓存于系统中的所有数据都可能会失效(即由于系统节点数目变少,客户端在请求某一对象时需要重新计算其hash值(通常与系统中的节点数目有关),由于hash值已经改变,所以很可能找不到保存该对象的服务器节点),因此一致性hash就显得至关重要,良好的分布式cahce系统中的一致性hash算法应该满足以下几个方面:

  平衡性()

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

  单调性()

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

  分散性()

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

  负载(Load)

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

  平滑性()

  平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致。解解决了传统hash算法增加机器后缓存大量miss的问题

  hashcode hash算法_java hash表分配算法_一致性hash算法 java

  redis目前主从复制过程有个很不好的地方就是:当slave和在同步过程中,网络出现问题,这时候slave要求重传而不是续传(重新生成一份RDB文件然后传输);这样当中数据很多的时候,影响会很大,所以一个下挂多个slave稳定性会下降。

  针对上述算法做一个简单的实现参考:/blog/

  一致性Hash实现:

  文章来源:https://zhuanlan.zhihu.com/p/34968328