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 } }