using System;
using System.Collections.Generic;
using System.Linq;
namespace POSV.LoadBalance
{
///
/// Consistent Hashing node ring implementation
///
///
public class ConsistentHash
{
private readonly SortedDictionary _circle;
private readonly int _virtualNodesFactor;
public ConsistentHash(SortedDictionary nodeHashCircle, int virtualNodesFactor)
{
if (nodeHashCircle == null)
throw new ArgumentNullException("nodeHashCircle");
if (virtualNodesFactor < 1)
throw new ArgumentException("virtualNodesFactor must be >= 1");
_circle = nodeHashCircle;
_virtualNodesFactor = virtualNodesFactor;
}
///
/// arrays for fast binary search access
///
private Tuple _ring = null;
private Tuple RingTuple
{
get { return _ring ?? (_ring = Tuple.Create(_circle.Keys.ToArray(), _circle.Values.ToArray())); }
}
///
/// Sorted hash values of the nodes
///
private int[] NodeHashRing
{
get { return RingTuple.Item1; }
}
///
/// NodeRing is the nodes sorted in the same order as , i.e. same index
///
private T[] NodeRing
{
get { return RingTuple.Item2; }
}
///
/// Add a node to the hash ring.
///
/// Note that is immutable and
/// this operation returns a new instance.
///
public ConsistentHash Add(T node)
{
return this + node;
}
///
/// Removes a node from the hash ring.
///
/// Note that is immutable and
/// this operation returns a new instance.
///
public ConsistentHash Remove(T node)
{
return this - node;
}
///
/// Converts the result of into an index in the
/// array.
///
///
///
private int Idx(int i)
{
if (i >= 0)
return i; //exact match
else
{
var j = Math.Abs(i + 1);
if (j >= NodeHashRing.Length)
return 0; //after last, use first
else
return j; //next node clockwise
}
}
///
/// Get the node responsible for the data key.
/// Can only be used if nodes exist in the node ring.
/// Otherwise throws .
///
public T NodeFor(byte[] key)
{
if (IsEmpty)
throw new InvalidOperationException(string.Format("Can't get node for [{0}] from an empty node ring", key));
return NodeRing[Idx(Array.BinarySearch(NodeHashRing, ConsistentHash.HashFor(key)))];
}
///
/// Get the node responsible for the data key.
/// Can only be used if nodes exist in the node ring.
/// Otherwise throws .
///
public T NodeFor(string key)
{
if (IsEmpty)
throw new InvalidOperationException(string.Format("Can't get node for [{0}] from an empty node ring", key));
return NodeRing[Idx(Array.BinarySearch(NodeHashRing, ConsistentHash.HashFor(key)))];
}
///
/// Is the node ring empty? i.e. no nodes added or all removed
///
public bool IsEmpty
{
get { return !_circle.Any(); }
}
#region Operator overloads
///
/// Add a node to the hash ring.
///
/// Note that is immutable and
/// this operation returns a new instance.
/// s
public static ConsistentHash operator +(ConsistentHash hash, T node)
{
var nodeHash = ConsistentHash.HashFor(node.GetHashCode().ToString());
var vNodeHashCircle = Enumerable.Range(1, hash._virtualNodesFactor)
.Select(x => new KeyValuePair(ConsistentHash.ConcatenateNodeHash(nodeHash, x), node));
var newNodeHashCircle = new SortedDictionary();
foreach (var item in hash._circle.Concat(vNodeHashCircle))
newNodeHashCircle.Add(item.Key, item.Value);
return new ConsistentHash(newNodeHashCircle, hash._virtualNodesFactor);
}
///
/// Removes a node from the hash ring.
///
/// Note that is immutable and
/// this operation returns a new instance.
///
public static ConsistentHash operator -(ConsistentHash hash, T node)
{
var nodeHash = ConsistentHash.HashFor(node.GetHashCode().ToString());
var vNodeHashCircle = Enumerable.Range(1, hash._virtualNodesFactor)
.Select(x => new KeyValuePair(ConsistentHash.ConcatenateNodeHash(nodeHash, x), node));
var newNodeHashCircle = new SortedDictionary();
foreach (var item in hash._circle.Except(vNodeHashCircle))
newNodeHashCircle.Add(item.Key, item.Value);
return new ConsistentHash(newNodeHashCircle, hash._virtualNodesFactor);
}
#endregion
}
///
/// Static helper class for creating instances.
///
public static class ConsistentHash
{
///
/// Factory method to create a instance.
///
public static ConsistentHash Create(IEnumerable nodes, int virtualNodesFactor)
{
var nodeHashCircle = new SortedDictionary();
foreach (var node in nodes)
{
var nodeHash = HashFor(node.GetHashCode().ToString());
var vNodeHashs = Enumerable.Range(1, virtualNodesFactor)
.Select(x => ConcatenateNodeHash(nodeHash, x)).ToList();
foreach (var vNodeHash in vNodeHashs)
nodeHashCircle.Add(vNodeHash, node);
}
return new ConsistentHash(nodeHashCircle, virtualNodesFactor);
}
#region Hashing methods
internal static int ConcatenateNodeHash(int nodeHash, int vnode)
{
unchecked
{
var h = MurmurHash.StartHash((uint)nodeHash);
h = MurmurHash.ExtendHash(h, (uint)vnode, MurmurHash.StartMagicA, MurmurHash.StartMagicB);
return (int)MurmurHash.FinalizeHash(h);
}
}
internal static int HashFor(string hashKey)
{
return MurmurHash.StringHash(hashKey);
}
internal static int HashFor(byte[] bytes)
{
return MurmurHash.ByteHash(bytes);
}
#endregion
}
}