[infinispan-commits] Infinispan SVN: r1031 - trunk/core/src/main/java/org/infinispan/distribution.
infinispan-commits at lists.jboss.org
infinispan-commits at lists.jboss.org
Tue Oct 27 10:33:06 EDT 2009
Author: manik.surtani at jboss.com
Date: 2009-10-27 10:33:06 -0400 (Tue, 27 Oct 2009)
New Revision: 1031
Added:
trunk/core/src/main/java/org/infinispan/distribution/ExperimentalDefaultConsistentHash.java
Log:
Checking this in, but this still needs work before we can use it as a default CH.
Added: trunk/core/src/main/java/org/infinispan/distribution/ExperimentalDefaultConsistentHash.java
===================================================================
--- trunk/core/src/main/java/org/infinispan/distribution/ExperimentalDefaultConsistentHash.java (rev 0)
+++ trunk/core/src/main/java/org/infinispan/distribution/ExperimentalDefaultConsistentHash.java 2009-10-27 14:33:06 UTC (rev 1031)
@@ -0,0 +1,361 @@
+package org.infinispan.distribution;
+
+import org.infinispan.marshall.Ids;
+import org.infinispan.marshall.Marshallable;
+import org.infinispan.remoting.transport.Address;
+import org.infinispan.util.Util;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import static java.lang.Math.min;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <a href = "http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent_hash.html">Consistent hashing
+ * algorithm</a>. Each target is entered into the pool <i>weight[i]</i>*<i>weightFactor</i> times. Where
+ * <i>weight[i]</i> and <i>weightFactor</i> are integers greater than zero.
+ * <p/>
+ * Based on akluge's impl on <a href-="http://www.vizitsolutions.com/ConsistentHashingCaching.html">http://www.vizitsolutions.com/ConsistentHashingCaching.html</a>
+ *
+ * @author akluge
+ * @author Manik Surtani
+ */
+ at Marshallable(externalizer = ExperimentalDefaultConsistentHash.Externalizer.class, id = Ids.DEFAULT_CONSISTENT_HASH)
+public class ExperimentalDefaultConsistentHash extends AbstractConsistentHash {
+ /**
+ * A Weight and weight factor of 1 gives one node per address. In future we may decide to make these configurable,
+ * to allow for virtual nodes and a better spread of state across nodes, but we need to ensure we deal with backups
+ * not falling on virtual nodes on the same cache instances first.
+ */
+ private static final int DEFAULT_WEIGHT = 1;
+ private static final int DEFAULT_WEIGHTFACTOR = 1;
+
+ private List<Address> nodes;
+ private List<Entry> pool;
+ private int poolSize;
+
+ public static class Externalizer implements org.infinispan.marshall.Externalizer {
+
+ public void writeObject(ObjectOutput output, Object object) throws IOException {
+ ExperimentalDefaultConsistentHash gch = (ExperimentalDefaultConsistentHash) object;
+ output.writeObject(gch.nodes);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object readObject(ObjectInput input) throws IOException, ClassNotFoundException {
+ List<Address> addresses = (List<Address>) input.readObject();
+ DefaultConsistentHash gch = new DefaultConsistentHash();
+ gch.setCaches(addresses);
+ return gch;
+ }
+ }
+
+ public List<Address> getCaches() {
+ return nodes;
+ }
+
+ public void setCaches(List<Address> caches) {
+ nodes = caches;
+ int numNodes = nodes.size();
+
+ int poolSize = 0;
+
+ for (int i = 0; i < numNodes; i++) {
+ poolSize += DEFAULT_WEIGHT * DEFAULT_WEIGHTFACTOR;
+ }
+ this.poolSize = poolSize;
+ pool = new ArrayList<Entry>(poolSize);
+
+ int numEntries = 0;
+ for (int i = 0; i < numNodes; i++) {
+ numEntries = add(nodes.get(i), DEFAULT_WEIGHT * DEFAULT_WEIGHTFACTOR, numEntries);
+ }
+ Collections.sort(pool);
+ nodes = getSortedCachesList();
+ }
+
+ private List<Address> getSortedCachesList() {
+ ArrayList<Address> caches = new ArrayList<Address>();
+ for (Entry e : pool) {
+ if (!caches.contains(e.address)) caches.add(e.address);
+ }
+ caches.trimToSize();
+ return caches;
+ }
+
+ /**
+ * Adds an Address to the pool of available addresses.
+ *
+ * @param node Address to be added to the hash pool.
+ * @param count An int giving the number of times the node is added to the pool. The count is greater than zero,
+ * and is likely greater than 10 if weights are used. The count for the i-th node is usually the
+ * <i>weights</i>[i]*<i>weightfactor</i>, if weights are used.
+ * @param position The position in the pool to begin adding node entries.
+ * @return position;
+ */
+ private int add(Address node, int count, int position) {
+ int hash;
+ String nodeName = node.toString();
+ for (int i = 0; i < count; i++) {
+ hash = hash((Integer.toString(i) + nodeName).getBytes());
+ pool.add(position++, new Entry(node, nodeName, i, hash));
+ }
+ return position;
+ }
+
+ /**
+ * The distance between the first entries in the address array for two caches, a1 and a2. Of questionable use when
+ * virtual nodes are employed.
+ *
+ * @param a1 The address of the first cache.
+ * @param a2 The address of the second cache.
+ * @return Am int containing the difference between these two indices.
+ */
+ public int getDistance(Address a1, Address a2) {
+ if (a1 == null || a2 == null) throw new NullPointerException("Cannot find the distance between null servers.");
+
+ int p1 = nodes.indexOf(a1);
+ if (p1 < 0)
+ throw new IllegalArgumentException("Address " + a1 + " not in the addresses list of this consistent hash impl!");
+
+ int p2 = nodes.indexOf(a2);
+ if (p2 < 0)
+ throw new IllegalArgumentException("Address " + a2 + " not in the addresses list of this consistent hash impl!");
+
+ if (p1 <= p2)
+ return p2 - p1;
+ else
+ return pool.size() - (p1 - p2);
+ }
+
+ /**
+ * Two hashes are adjacent if they are next to each other in the consistent hash.
+ *
+ * @param a1 The address of the first cache.
+ * @param a2 The address of the second cache.
+ * @return A boolean, true if they are adjacent, false if not.
+ */
+ public boolean isAdjacent(Address a1, Address a2) {
+ int distance = getDistance(a1, a2);
+ return distance == 1 || distance == pool.size() - 1;
+ }
+
+ public List<Address> locate(Object key, int replCount) {
+ if (key == null) throw new NullPointerException("Attempt to get with null key");
+
+ int clusterSize = pool.size();
+ int numCopiesToFind = min(replCount, clusterSize);
+ int hashValue = hash(key);
+ return locate(hashValue, numCopiesToFind, replCount);
+ }
+
+ /**
+ * Returns a List of <i>numCopiesToFind</i> unique Addresses.
+ *
+ * @param hashValue An int, usually a hash, to be mapped to a bin via the CH.
+ * @param numCopiesToFind number of copies to find
+ * @param replCount replication count
+ * @return Returns a List of <i>numCopiesToFind</i> unique Addresses.
+ */
+ private List<Address> locate(int hashValue, int numCopiesToFind, int replCount) {
+ // Stop looking if we have checked the entire pool.
+ int checked = 0;
+ // Start looking at the first (primary) node for entries for this value.
+ int inode = findNearestNodeInPool(hashValue);
+ List<Address> nodes = new ArrayList<Address>(numCopiesToFind);
+
+ while (nodes.size() < replCount && checked < poolSize) {
+ Entry poolEntry;
+ if ((poolEntry = pool.get(inode)) != null && nodes.indexOf(poolEntry.address) < 0) {
+ nodes.add(poolEntry.address);
+ }
+ inode = (++inode) % poolSize;
+ checked++;
+ }
+ return nodes;
+ }
+
+ /**
+ * Find a target for a hash key within the pool of node Entries. We search within a slice of the array bounded by
+ * lowerBound and upperBound. Further we assume that lowerBound and upperBound are small enough that their sum will
+ * not overflow an int.
+ * <p/>
+ *
+ * @param hash The desired hash to locate.
+ * @return An int giving the index of the desired entry in the list of targets. If the target is not found, then
+ * -(lowerBound +1) will be returned, where lowerBound is the lower bound of the search after possibly
+ * several iterations.
+ */
+ private int binarySearch(int hash) {
+ int lowerBound = 0;
+ int upperBound = pool.size() - 1;
+ while (lowerBound <= upperBound) {
+ // Fast div by 2. We assume that the number of targets is small enough
+ // that the sum will not overflow an int.
+ int mid = (lowerBound + upperBound) >>> 1;
+ int currentHash = pool.get(mid).hash;
+
+ if (currentHash < hash) {
+ lowerBound = mid + 1;
+ } else if (currentHash > hash) {
+ upperBound = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+ // The +1 ensures that the return value is negative, even when the hash
+ // is off the left edge of the array.
+ return -(lowerBound + 1);
+ }
+
+ /**
+ * Finds the lowest index into the pool ArrayList such that the hash of the i-th entry >= hash.
+ *
+ * @param hash The hash being mapped to a bin via the consistent hash.
+ * @return An int, the lowest index into the target array such that the hash of the i-th entry >= hash.
+ */
+ private int findNearestNodeInPool(int hash) {
+ // Find the index of the node - or at least a near one.
+ // We only search up to targets.length-1. If the element
+ // is greater than the last entry in the list, then map
+ // it to the first one.
+ int nodeIndex = binarySearch(hash);
+
+ // If the returned value is less than zero, then no exact match was found.
+ if (nodeIndex < 0) {
+ // The value returned is -(lowerBound +1), we want the lower bound back.
+ nodeIndex = -(nodeIndex + 1);
+
+ // If hash is greater than the last entry, wrap around to the first.
+ if (nodeIndex >= pool.size()) {
+ nodeIndex = 0;
+ }
+ }
+
+ return nodeIndex;
+ }
+
+ /**
+ * Use the objects built in hash to obtain an initial value, then use a second four byte hash to obtain a more
+ * uniform distribution of hash values. This uses a <a href = "http://burtleburtle.net/bob/hash/integer.html">4-byte
+ * (integer) hash</a>, which produces well distributed values even when the original hash produces thghtly clustered
+ * values.
+ * <p/>
+ * It is important that the object implement its own hashcode, and not use the Object hashcode.
+ *
+ * @param object object to hash
+ * @return an appropriately spread hash code
+ */
+ private int hash(Object object) {
+ int hash = object.hashCode();
+
+ hash = (hash + 0x7ED55D16) + (hash << 12);
+ hash = (hash ^ 0xc761c23c) ^ (hash >> 19);
+ hash = (hash + 0x165667b1) + (hash << 5);
+ hash = (hash + 0xd3a2646c) ^ (hash << 9);
+ hash = (hash + 0xfd7046c5) + (hash << 3);
+ hash = (hash ^ 0xb55a4f09) ^ (hash >> 16);
+
+ return hash;
+ }
+
+ /**
+ * @return A String representing the object pool.
+ */
+ @Override
+ public String toString() {
+ return " pool: " + pool;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null
+ || !(other instanceof ExperimentalDefaultConsistentHash)) {
+ return false;
+ }
+
+ ExperimentalDefaultConsistentHash otherHash = (ExperimentalDefaultConsistentHash) other;
+ return Util.safeEquals(pool, otherHash.pool);
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ for (Entry e : pool) hashCode = 31 * hashCode + e.hash;
+ return hashCode;
+ }
+
+ /**
+ * An entry into a consistent hash. It wraps the original object, the object's hash as used to generate the
+ * consistent hash, the value extracted from the object used to generate the hash, and the modifier used to
+ * differentiate the hash.
+ */
+ public static class Entry implements Comparable<Entry> {
+ public final int differentiator;
+ public final int hash;
+ public final Address address;
+ public final String string;
+
+ public Entry(Address address, String string, int differentiator, int hash) {
+ this.differentiator = differentiator;
+ this.hash = hash;
+ this.address = address;
+ this.string = string;
+ }
+
+ /**
+ * Compare this Entry with another Entry. First the hash values are compared, then the differentiator is compared.
+ * if the hash values are equal.
+ *
+ * @param other An Entry object to be compared with this object. Returns <ul> <li>-1 if this Entry is less than
+ * the other Entry.</li> <li>0 if they are equal.</li> <li>+1 if this Entry is greater than the
+ * other Entry.</li> </ul>
+ * @return
+ */
+ public int compareTo(Entry other) {
+ if (this.hash < other.hash) {
+ return -1;
+ }
+
+ if (this.hash > other.hash) {
+ return 1;
+ }
+
+ if (this.differentiator < other.differentiator) {
+ return -1;
+ }
+
+ if (this.differentiator > other.differentiator) {
+ return +1;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Entry) {
+ Entry otherEntry = (Entry) other;
+
+ return hash == otherEntry.hash
+ && differentiator == otherEntry.differentiator
+ && address.equals(otherEntry.address);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+
+ @Override
+ public String toString() {
+ return string + ":" + Integer.toHexString(hash);
+ }
+ }
+}
\ No newline at end of file
Property changes on: trunk/core/src/main/java/org/infinispan/distribution/ExperimentalDefaultConsistentHash.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
More information about the infinispan-commits
mailing list