Java tutorial
/* * Copyright 2016 Claymore Minds Limited and Niche Solutions (UK) Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.viewserver.core; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import java.nio.charset.Charset; import java.util.Collection; import java.util.SortedMap; import java.util.TreeMap; /** * This is an implementation of a consistent hash. T is the type of a bin. * * It is mostly copied from Tom White's implementation found here: * http://www.lexemetech.com/2007/11/consistent-hashing.html * * Blog comments mention that there may be a bug in this implementation -- if * there is a key collision we may lose bins. Probabilistically this is small, * and even smaller with a higher more replication factor. This could be made * even rarer by enlarging the circle by using Long instead of Integer. * * getNBins and getNUniqBins return ordered lists of bins for a particular * object. This is useful for assigning backups if the first bin fails. * * This datastructure is not threadsafe. */ public class ConsistentHash { // when looking for n unique bins, give up after a streak of MAX_DUPES // duplicates public final static int MAX_DUPES = 10; private final Charset charset = Charset.forName("utf8"); // # of times a bin is replicated in hash circle. (for better load balancing) private final int numberOfReplicas; private final HashFunction hashFunction; private final SortedMap<Long, Integer> circle = new TreeMap<>(); public ConsistentHash(int numberOfReplicas, Collection<Integer> nodes) { this(Hashing.murmur3_32(), numberOfReplicas, nodes); } public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, Collection<Integer> nodes) { this.hashFunction = hashFunction; this.numberOfReplicas = numberOfReplicas; for (int node : nodes) { addBin(node); } } /** * Add a new bin to the consistent hash * * This assumes that the bin's toString method is immutable. * * This is not thread safe. */ public void addBin(int bin) { for (int i = 0; i < numberOfReplicas; i++) { // The string addition forces each replica to have different hash circle.put(hashFunction.hashString(Long.toString(bin) + i, charset).padToLong(), bin); } } /** * Remove a bin from the consistent hash * * This assumes that the bin's toString method is immutable. * * This is not thread safe. */ public void removeBin(int bin) { for (int i = 0; i < numberOfReplicas; i++) { // The string addition forces each replica to be different. This needs // to resolve to the same keys as addBin. circle.remove(hashFunction.hashString(Long.toString(bin) + i, charset).padToLong()); } } /** * This returns the closest bin for the object. If the object is the bin it * should be an exact hit, but if it is a value traverse to find closest * subsequent bin. */ public int getBinFor(long key) { if (circle.isEmpty()) { return Integer.MIN_VALUE; } long hash = hashFunction.hashLong(key).padToLong(); Integer bin = circle.get(hash); if (bin == null) { // inexact match -- find the next value in the circle SortedMap<Long, Integer> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); bin = circle.get(hash); } return bin; } // /** // * This returns the closest n bins in order for the object. There may be // * duplicates. // */ // public List<Integer> getNBinsFor(long key, int n) { // if (circle.isEmpty()) { // return Collections.<T> emptyList(); // } // // List<T> list = new ArrayList<T>(n); // int hash = hashFunction.hash(key); // for (int i = 0; i < n; i++) { // if (!circle.containsKey(hash)) { // // go to next element. // SortedMap<Integer, T> tailMap = circle.tailMap(hash); // hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); // } // list.add(circle.get(hash)); // // // was a hit so we increment and loop to find the next bin in the // // circle // hash++; // } // return list; // } // // /** // * This returns the closest n bins in order for the object. There is extra // * code that forces the bin values to be unique. // * // * This will return a list that has all the bins (and is smaller than n) if n // * > number of bins. // */ // public List<T> getNUniqueBinsFor(Object key, int n) { // if (circle.isEmpty()) { // return Collections.<T> emptyList(); // } // // List<T> list = new ArrayList<T>(n); // int hash = hashFunction.hash(key); // int duped = 0; // for (int i = 0; i < n; i++) { // if (!circle.containsKey(hash)) { // // go to next element. // SortedMap<Integer, T> tailMap = circle.tailMap(hash); // hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); // } // T candidate = circle.get(hash); // if (!list.contains(candidate)) { // duped = 0; // list.add(candidate); // } else { // duped++; // i--; // try again. // if (duped > MAX_DUPES) { // i++; // we've been duped too many times, just skip to next, returning // // fewer than n // } // // } // // // find the next element in the circle // hash++; // } // return list; // } }