[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