5分でコンシステントハッシュアルゴリズムを理解する

コンシステントハッシュアルゴリズムは、マサチューセッツ工科大学のKarger et al。によって、分散キャッシュの解決において1997年に提案されました。設計目標は、インターネットのホットスポット問題を解決することです。当初の意図はCARPと非常に似ています。コンシステントハッシュは、CARP使用される単純なハッシュアルゴリズムによって引き起こされる問題を修正するため、DHTをP2P環境に真に適用できます。

しかし現在、コンシステントハッシュアルゴリズムは分散システムでも広く使用されています。memcachedキャッシュデータベースを研究したことのある人なら誰でも、memcachedサーバー自体が分散キャッシュの一貫性を提供しないことを知っていますが、クライアントによって提供されます。次の手順を使用してコンシステントハッシュを計算します。

  1. まず、memcachedサーバ(ノード)のハッシュ値を見つけ、それを置くの連続で0-2 32

  2. 次に、同じ方法を使用して、データを格納するキーのハッシュ値を見つけ、それを同じ円にマップします。

  3. 次に、データがマップされている場所から時計回りの検索を開始し、最初に見つかったサーバーにデータを保存します。2 32を超えてもサーバーが見つからない場合は、最初のmemcachedサーバーに保存されます。

上記の状態からmemcachedサーバーを追加します。残りの分散アルゴリズムは、キーを保存するサーバーの大幅な変更によりキャッシュヒット率に影響しますが、コンシステントハッシュでは、連続体の反時計回り方向の最初のサーバーのキーのみが影響を受けます。 :

コンシステントハッシュプロパティ

分散システムの各ノードに障害が発生する可能性があり、新しいノードが動的に増加する可能性があることを考慮すると、システム内のノードの数が変化した場合でも、システムが外部に優れたサービスを提供できるようにする方法を検討する価値があります。特に、分散キャッシュシステムを設計する際に、特定のサーバーに障害が発生した場合、システム全体で、一貫性を確保するために適切なアルゴリズムが使用されていない場合、システムにキャッシュされたすべてのデータが無効になる可能性があります(つまり、システムノード、クライアントはオブジェクトを要求するときにハッシュ値(通常はシステム内のノードの数に関連する)を再計算する必要があります。ハッシュ値が変更されているため、オブジェクトを保存するサーバーノードが見つからない可能性があります) 、したがって、一貫性のあるハッシュは次のようになります。優れた分散型cahceシステムの一貫性のあるハッシュアルゴリズムは、次の側面を満たす必要があります。

  • 残高

バランスとは、ハッシュの結果を可能な限りすべてのバッファーに分散できるため、すべてのバッファースペースを使用できることを意味します。多くのハッシュアルゴリズムがこの条件を満たすことができます。

  • 単調性

単調性とは、あるコンテンツがハッシュによって対応するバッファに割り当てられ、新しいバッファがシステムに追加された場合、ハッシュの結果により、元の割り当てられたコンテンツを新しいコンテンツにマッピングできることを保証できるはずです。バッファに、古いバッファセットの他のバッファにマップされません。単純なハッシュアルゴリズムは、最も単純な線形ハッシュなどの単調性の要件を満たすことができないことがよくあります。x=(ax + b)mod(P)、上記の式で、Pはバッファー全体のサイズを表します。バッファサイズが(P1からP2に)変更されると、元のハッシュ結果がすべて変更され、単調性の要件を満たしていないことを確認するのは難しくありません。ハッシュ結果の変更は、バッファスペースが変更されたときに、システム内のすべてのマッピング関係を更新する必要があることを意味します。P2Pシステムでは、バッファの変更は、システムに参加または終了するピアと同等です。この状況は、P2Pシステムで頻繁に発生し、膨大な計算と送信の負荷が発生します。単調性には、この状況に対処できるハッシュアルゴリズムが必要です。

  • 展開する

分散環境では、端末はすべてのバッファを認識​​せず、一部のバッファのみを認識します。端末がハッシュプロセスを介してコンテンツをバッファにマッピングする場合、異なる端末から見たバッファ範囲が異なる可能性があり、結果として一貫性のないハッシュ結果が得られます。最終的な結果として、同じコンテンツがバッファ内の異なる端末によって異なる端末にマッピングされます。 。同じコンテンツが異なるバッファに保存され、システムストレージの効率が低下するため、この状況は明らかに回避する必要があります。分散の定義は、上記の状況の発生の重大度です。優れたハッシュアルゴリズムは、不整合を可能な限り回避できる、つまり分散を最小限に抑えることができる必要があります。

  • 負荷

負荷の問題は、実際には分散の問題を別の角度から見ています。異なる端末が同じコンテンツを異なるバッファにマップする可能性があるため、特定のバッファが異なるユーザーによって異なるコンテンツにマップされることもあります。分散化と同様に、この状況も回避する必要があるため、優れたハッシュアルゴリズムを使用すると、バッファーの負荷を可能な限り減らすことができます。

  • 滑らかさ

スムーズ性とは、キャッシュサーバーの数のスムーズな変更とキャッシュオブジェクトのスムーズな変更が一貫していることを意味します。

原理

基本概念

コンシステントハッシュ法は、「コンシステントハッシュ法とランダムツリー:ワールドワイドウェブ上のホットスポットを緩和するための分散キャッシングプロトコル」という論文で最初に提案されました。簡単に言うと、コンシステントハッシュ法は、ハッシュ値空間全体を仮想円に編成します。たとえば、ハッシュ関数Hの値空間が0-2 ^ 32 -1(つまり、ハッシュ値が32ビット)であると仮定します。シンボルシェーピング)、ハッシュスペースリング全体は次のとおりです。

画像

空間全体が時計回りに整理されています。0と2 32 -1ゼロ点の方向に一致します。

次のステップでは、各サーバーがハッシュを使用してハッシュを実行します。具体的には、サーバーのIP名またはホスト名をハッシュのキーとして選択して、各マシンがハッシュリング上の位置を判別できるようにします。 IPアドレスを使用してハッシュした後のリングスペースは次のとおりです。

次に、次のアルゴリズムを使用して、対応するサーバーにアクセスするためのデータを見つけます。同じ関数Hashを使用して、データキーのハッシュ値を計算し、リング上のデータの位置をこの位置から時計回りに「歩く」まで決定します。リングに沿って、最初に遭遇したサーバーは、それが見つけなければならないサーバーです。

たとえば、オブジェクトA、オブジェクトB、オブジェクトC、オブジェクトDの4つのデータオブジェクトがあります。ハッシュ計算後のリング空間での位置は次のとおりです。

 

コンシステントハッシュアルゴリズムによれば、データAはノードAに割り当てられ、BはノードBに割り当てられ、CはノードCに割り当てられ、DはノードDに割り当てられます。

以下は、コンシステントハッシュアルゴリズムのフォールトトレランスとスケーラビリティを分析します。ここで、残念ながらノードCがダウンしていると仮定します。この時点では、オブジェクトA、B、およびDは影響を受けず、CオブジェクトのみがノードDに再配置されていることがわかります。一般に、コンシステントハッシュアルゴリズムでは、サーバーが利用できない場合、影響を受けるデータは、リングスペース内の前のサーバー(つまり、反時計回りに歩いたときに最初に検出されたサーバー)のサーバーのみです。サーバー間のデータ)、他の人は影響を受けません。

次の図に示すように、サーバーノードXをシステムに追加する場合は、次の別の状況を検討してください。

画像

この時点では、オブジェクトA、B、およびDは影響を受けず、オブジェクトCのみを新しいノードXに再配置する必要があります。一般に、コンシステントハッシュアルゴリズムでは、1つのサーバーが追加された場合、影響を受けるデータは、リングスペース内の前のサーバー(つまり、反時計回りに歩いたときに最初に検出されたサーバー)の新しいサーバーのみです)、他のデータは影響を受けません。

要約すると、コンシステントハッシュアルゴリズムは、ノードの増減のためにリングスペース内のデータのごく一部を再配置するだけでよく、これは優れたフォールトトレランスとスケーラビリティを備えています。

さらに、サービスノードが少なすぎると、コンシステントハッシュアルゴリズムにより、ノードの分散が不均一になるためにデータスキューが発生する可能性があります。たとえば、システムにはサーバーが2つしかなく、リングの分散は次のようになります。

画像

現時点では、必然的に大量のデータがノードAに集中し、ノードBにはごく少量しか配置されません。このデータスキューの問題を解決するために、コンシステントハッシュアルゴリズムは仮想ノードメカニズムを導入します。つまり、サービスノードごとに複数のハッシュが計算され、サービスノードは仮想ノードと呼ばれる各計算結果の場所に配置されます。特定の方法は、サーバーのIP名またはホスト名の後に番号を追加することで実現できます。たとえば、上記の場合、サーバーごとに3つの仮想ノードを計算できるため、「ノードA#1」、「ノードA#2」、「ノードA#3」、「ノードB#1」、「ノードA#1 "は別々に計算できます。B#2"と "ノードB#3"のハッシュ値は6つの仮想ノードを形成します:

同時に、データ測位アルゴリズムは変更されませんが、仮想ノードを実際のノードにマッピングする追加の手順があります。たとえば、「ノードA#1」、「ノードA」の3つの仮想ノードに配置されたデータ#2 "と"ノードA#3 "はすべてノードAに移動します。これにより、サービスノードが少ない場合のデータスキューの問題が解決されます。実際のアプリケーションでは、仮想ノードの数は通常32以上に設定されているため、少数のサービスノードでも比較的均一なデータ分散を実現できます。

JAVAコードの実装

パッケージorg.java.base.hash; 
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 int numberOfReplicas; //ノードの複製係数、実際のノードの数* numberOfReplicas = 
 //仮想の数ノード
 privatefinal SortedMap <Integer、T> circle = new TreeMap <Integer、T>(); //仮想ノードのハッシュ値から実ノードへのマッピングを格納しますpublicConsistentHash 

 (int numberOfReplicas、
 Collection <T>ノード) { 
 this.numberOfReplicas = numberOfReplicas; 
 for(T node:nodes){ 
 add(node); 
 }
 }

 public void add(T node){ 
 for(int i = 0; i <numberOfReplicas; i ++){ 
 // numberOfReplicas仮想ノードに対応する実際のマシンノードノードの場合
 / * 
 *異なる仮想ノード(iが異なる)は異なるハッシュ値を持ちます、ただし、これらはすべて同じ実際のマシンノードに対応します
 *仮想ノードは通常、リング上に均等に分散され、データは時計回りの仮想ノードに格納されます
 * / 
 String nodestr = node.toString()+ i; 
 int hashcode = nodestr。 hashCode(); 
 System.out.println( "hashcode:" + hashcode); 
 circle.put(hashcode、node); 
 
 } 
 } 

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

 / * 
 *最も近い時計回りのノードを取得し、指定されたキーに従ってハッシュ
 *を取得してから、最も近い時計回りのノードを取得します。対応する実際のノード仮想ノードへ
 *次に、実際のノードからデータを取得します
 * / 
 public T get(Object key){ 
 if(circle.isEmpty())
 return null; 
 int hash = key.hashCode(); //ノードは文字列で表され、ハッシュリング内
 のノードのhashCodeSystem.outを取得します.println( "hashcode ----->:" + hash); 
 if(!circle.containsKey(hash)){//データがリング内の2つの仮想マシン間でマップされている場合、マシンを見つける必要があります時計回りの方向
 SortedMap <Integer、T> tailMap = circle.tailMap(hash); 
 hash = tailMap.isEmpty()?Circle.firstKey():tailMap.firstKey(); 
 } 
 return circle.get(hash); 
 } 

 public long getSize (){ 
 return circle.size(); 
 } 
 
 / **
 ハッシュリング全体での各仮想ノードの位置を表示します
 * / 
 public void testBalance(){ 
 Set <Integer> sets = circle.keySet(); //すべて取得TreeMapキーの
 SortedSet <整数> sortedSets = new TreeSet <整数>(セット); //将获得的キー集合排序 
 public static void main(String [] args){
 for(整数hashCode:sortedSets){ 
 System.out.println(hashCode); 
 } 
 
 System.out.println( "----各場所の距離は次のとおりです:----"); 
 / **
 查看相邻两TThashCode的差值
 * / 
 Iterator <Integer> it = sortSets.iterator(); 
 Iterator <Integer> it2 = sortSets.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); 
 } 
 }
 ノード。
 
 Set <String>ノード= new HashSet <String>(); 
 nodes.add( "A"); 
 nodes.add( "B"); 
 
 ConsistentHash <String> ConsistentHash = new ConsistentHash <String>(2、nodes); 
 ConsistentHash.add( "D"); 
 
 System.out.println( "ハッシュサークルサイズ:" + ConsistentHash.getSize()); 
 System.out.println( "各ノードの場所は次のとおりです:"); 
 ConsistentHash.testBalance(); 
 
 文字列ノード= consistentHash.get( "apple"); 
 System.out.println( "node ----------->:" + node); 
 } 
 
}


おすすめ

転載: blog.51cto.com/15082402/2644368