2011-05-17 21:15:27 +02:00
|
|
|
/**
|
2012-01-19 18:21:06 +01:00
|
|
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
2011-05-17 21:15:27 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package akka.routing
|
|
|
|
|
|
2012-09-17 20:46:53 +02:00
|
|
|
import scala.collection.immutable.SortedMap
|
|
|
|
|
import scala.reflect.ClassTag
|
2012-09-14 11:06:55 +02:00
|
|
|
import java.util.Arrays
|
2012-09-08 20:54:16 +02:00
|
|
|
|
2011-05-17 21:15:27 +02:00
|
|
|
/**
|
2012-09-14 13:53:04 +02:00
|
|
|
* Consistent Hashing node ring implementation.
|
2011-05-17 21:15:27 +02:00
|
|
|
*
|
2012-09-08 20:54:16 +02:00
|
|
|
* A good explanation of Consistent Hashing:
|
|
|
|
|
* http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent_hash.html
|
|
|
|
|
*
|
|
|
|
|
* Note that toString of the ring nodes are used for the node
|
|
|
|
|
* hash, i.e. make sure it is different for different nodes.
|
|
|
|
|
*
|
2011-05-17 21:15:27 +02:00
|
|
|
*/
|
2012-10-10 15:23:18 +02:00
|
|
|
class ConsistentHash[T: ClassTag] private (nodes: SortedMap[Int, T], val virtualNodesFactor: Int) {
|
2012-09-08 20:54:16 +02:00
|
|
|
|
2012-09-09 16:47:43 +02:00
|
|
|
import ConsistentHash._
|
2011-05-17 21:15:27 +02:00
|
|
|
|
2012-09-13 18:06:35 +02:00
|
|
|
if (virtualNodesFactor < 1) throw new IllegalArgumentException("virtualNodesFactor must be >= 1")
|
2011-05-17 21:15:27 +02:00
|
|
|
|
2012-09-17 20:46:53 +02:00
|
|
|
// arrays for fast binary search and access
|
|
|
|
|
// nodeHashRing is the sorted hash values of the nodes
|
|
|
|
|
// nodeRing is the nodes sorted in the same order as nodeHashRing, i.e. same index
|
|
|
|
|
private val (nodeHashRing: Array[Int], nodeRing: Array[T]) = {
|
|
|
|
|
val (nhr: Seq[Int], nr: Seq[T]) = nodes.toSeq.unzip
|
|
|
|
|
(nhr.toArray, nr.toArray)
|
2012-09-14 11:06:55 +02:00
|
|
|
}
|
|
|
|
|
|
2012-09-08 20:54:16 +02:00
|
|
|
/**
|
2012-09-13 18:06:35 +02:00
|
|
|
* Adds a node to the node ring.
|
2012-09-09 16:47:43 +02:00
|
|
|
* Note that the instance is immutable and this
|
|
|
|
|
* operation returns a new instance.
|
2012-09-08 20:54:16 +02:00
|
|
|
*/
|
2012-09-13 18:06:35 +02:00
|
|
|
def :+(node: T): ConsistentHash[T] =
|
2012-09-14 11:06:55 +02:00
|
|
|
new ConsistentHash(nodes ++ ((1 to virtualNodesFactor) map { r ⇒ (nodeHashFor(node, r) -> node) }), virtualNodesFactor)
|
2011-05-17 21:15:27 +02:00
|
|
|
|
2012-09-08 20:54:16 +02:00
|
|
|
/**
|
2012-09-13 18:06:35 +02:00
|
|
|
* Adds a node to the node ring.
|
2012-09-09 16:47:43 +02:00
|
|
|
* Note that the instance is immutable and this
|
|
|
|
|
* operation returns a new instance.
|
2012-09-08 20:54:16 +02:00
|
|
|
* JAVA API
|
|
|
|
|
*/
|
2012-09-09 16:47:43 +02:00
|
|
|
def add(node: T): ConsistentHash[T] = this :+ node
|
2012-09-08 20:54:16 +02:00
|
|
|
|
|
|
|
|
/**
|
2012-09-13 18:06:35 +02:00
|
|
|
* Removes a node from the node ring.
|
2012-09-09 16:47:43 +02:00
|
|
|
* Note that the instance is immutable and this
|
|
|
|
|
* operation returns a new instance.
|
2012-09-08 20:54:16 +02:00
|
|
|
*/
|
2012-09-13 18:06:35 +02:00
|
|
|
def :-(node: T): ConsistentHash[T] =
|
2012-09-14 11:06:55 +02:00
|
|
|
new ConsistentHash(nodes -- ((1 to virtualNodesFactor) map { r ⇒ nodeHashFor(node, r) }), virtualNodesFactor)
|
2011-05-17 21:15:27 +02:00
|
|
|
|
2012-09-08 20:54:16 +02:00
|
|
|
/**
|
2012-09-13 18:06:35 +02:00
|
|
|
* Removes a node from the node ring.
|
2012-09-09 16:47:43 +02:00
|
|
|
* Note that the instance is immutable and this
|
|
|
|
|
* operation returns a new instance.
|
2012-09-08 20:54:16 +02:00
|
|
|
* JAVA API
|
|
|
|
|
*/
|
2012-09-09 16:47:43 +02:00
|
|
|
def remove(node: T): ConsistentHash[T] = this :- node
|
2012-09-08 20:54:16 +02:00
|
|
|
|
2012-09-17 09:51:15 +02:00
|
|
|
// converts the result of Arrays.binarySearch into a index in the nodeRing array
|
|
|
|
|
// see documentation of Arrays.binarySearch for what it returns
|
|
|
|
|
private def idx(i: Int): Int = {
|
|
|
|
|
if (i >= 0) i // exact match
|
|
|
|
|
else {
|
|
|
|
|
val j = math.abs(i + 1)
|
|
|
|
|
if (j >= nodeHashRing.length) 0 // after last, use first
|
|
|
|
|
else j // next node clockwise
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-08 20:54:16 +02:00
|
|
|
/**
|
|
|
|
|
* Get the node responsible for the data key.
|
2012-09-13 18:06:35 +02:00
|
|
|
* Can only be used if nodes exists in the node ring,
|
2012-09-08 20:54:16 +02:00
|
|
|
* otherwise throws `IllegalStateException`
|
|
|
|
|
*/
|
2011-05-17 21:15:27 +02:00
|
|
|
def nodeFor(key: Array[Byte]): T = {
|
2012-09-13 18:06:35 +02:00
|
|
|
if (isEmpty) throw new IllegalStateException("Can't get node for [%s] from an empty node ring" format key)
|
2012-09-14 11:06:55 +02:00
|
|
|
|
2012-09-17 09:51:15 +02:00
|
|
|
nodeRing(idx(Arrays.binarySearch(nodeHashRing, hashFor(key))))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the node responsible for the data key.
|
|
|
|
|
* Can only be used if nodes exists in the node ring,
|
|
|
|
|
* otherwise throws `IllegalStateException`
|
|
|
|
|
*/
|
|
|
|
|
def nodeFor(key: String): T = {
|
|
|
|
|
if (isEmpty) throw new IllegalStateException("Can't get node for [%s] from an empty node ring" format key)
|
|
|
|
|
|
|
|
|
|
nodeRing(idx(Arrays.binarySearch(nodeHashRing, hashFor(key))))
|
2012-09-08 20:54:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-09-13 18:06:35 +02:00
|
|
|
* Is the node ring empty, i.e. no nodes added or all removed.
|
2012-09-08 20:54:16 +02:00
|
|
|
*/
|
2012-09-14 11:06:55 +02:00
|
|
|
def isEmpty: Boolean = nodes.isEmpty
|
2012-09-08 20:54:16 +02:00
|
|
|
|
2012-09-09 16:47:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object ConsistentHash {
|
2012-09-17 20:46:53 +02:00
|
|
|
def apply[T: ClassTag](nodes: Iterable[T], virtualNodesFactor: Int): ConsistentHash[T] = {
|
|
|
|
|
new ConsistentHash(SortedMap.empty[Int, T] ++
|
2012-09-13 18:06:35 +02:00
|
|
|
(for (node ← nodes; vnode ← 1 to virtualNodesFactor) yield (nodeHashFor(node, vnode) -> node)),
|
|
|
|
|
virtualNodesFactor)
|
2012-09-09 16:47:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Factory method to create a ConsistentHash
|
|
|
|
|
* JAVA API
|
|
|
|
|
*/
|
2012-09-17 20:46:53 +02:00
|
|
|
def create[T](nodes: java.lang.Iterable[T], virtualNodesFactor: Int): ConsistentHash[T] = {
|
2012-09-09 16:47:43 +02:00
|
|
|
import scala.collection.JavaConverters._
|
2012-09-17 20:46:53 +02:00
|
|
|
apply(nodes.asScala, virtualNodesFactor)(ClassTag(classOf[Any].asInstanceOf[Class[T]]))
|
2012-09-09 16:47:43 +02:00
|
|
|
}
|
|
|
|
|
|
2012-09-13 18:06:35 +02:00
|
|
|
private def nodeHashFor(node: Any, vnode: Int): Int =
|
|
|
|
|
hashFor((node + ":" + vnode).getBytes("UTF-8"))
|
2011-05-17 21:15:27 +02:00
|
|
|
|
2012-09-13 18:06:35 +02:00
|
|
|
private def hashFor(bytes: Array[Byte]): Int = MurmurHash.arrayHash(bytes)
|
2011-05-17 21:15:27 +02:00
|
|
|
|
2012-09-17 09:51:15 +02:00
|
|
|
private def hashFor(string: String): Int = MurmurHash.stringHash(string)
|
|
|
|
|
}
|