package com.rubiconproject.oss.kv.distributed.impl; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import com.rubiconproject.oss.kv.distributed.Node; import com.rubiconproject.oss.kv.distributed.NodeChangeListener; import com.rubiconproject.oss.kv.distributed.NodeLocator; import com.rubiconproject.oss.kv.distributed.hashing.HashAlgorithm; import com.rubiconproject.oss.kv.distributed.hashing.HashRing; import com.rubiconproject.oss.kv.distributed.hashing.KetamaHashAlgorithm; /** * Modified from the spy memcached client, which has the following license. * * This is roughly comparable to partitioning Strategy 1 in dynamo. Read the * notes in that paper. * * Copyright (c) 2006-2009 Dustin Sallings <dustin@spy.net> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * @author Dustin Sallings <dustin@spy.net> * @author Sam Tingleff <sam@tingleff.com> */ public class KetamaNodeLocator implements NodeLocator, NodeChangeListener { private static final int NUM_REPS = 160; private KetamaHashAlgorithm hashAlg = new KetamaHashAlgorithm(); private volatile HashRing<Long, Node> ketamaNodes = null; private volatile int nodeCount = 0; public KetamaNodeLocator() { } public void setActiveNodes(List<Node> nodes) { ketamaNodes = build(nodes); nodeCount = nodes.size(); } public int getPrimaryNode(HashAlgorithm hashAlg, String key) { return 0; } public List<Node> getPreferenceList(final HashAlgorithm hashAlg, final String key, int count) { Iterator<Node> iter = new KetamaIterator(ketamaNodes, key, ketamaNodes .size()); List<Node> results = new ArrayList<Node>(count); while ((results.size() < count) && (iter.hasNext())) { Node n = iter.next(); if (!results.contains(n)) results.add(n); } return results; } public List<Node> getFullPreferenceList(HashAlgorithm hashAlg, String key) { return getPreferenceList(hashAlg, key, nodeCount); } private HashRing<Long, Node> build(List<Node> nodes) { HashRing<Long, Node> ketamaNodes = new HashRing<Long, Node>(nodes .size()); for (Node node : nodes) { String nodeIdentifier = node.getSalt(); for (int i = 0; i < NUM_REPS / 4; ++i) { byte[] digest = hashAlg.md5(nodeIdentifier + "-" + i); for (int h = 0; h < 4; h++) { Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24) | ((long) (digest[2 + h * 4] & 0xFF) << 16) | ((long) (digest[1 + h * 4] & 0xFF) << 8) | (digest[h * 4] & 0xFF); ketamaNodes.put(k, node); } } } return ketamaNodes; } private Node getNodeForKey(final HashRing<Long, Node> ketamaNodes, long hash) { Node rv = null; // dsallings: // "Java 1.6 adds a ceilingKey method, but I'm still stuck in 1.5 // in a lot of places, so I'm doing this myself." // sam: I'm not. modified to use jdk 1.6 ceilingEntry in the HashRing class Map.Entry<Long, Node> entry = ketamaNodes.place(hash); if (entry != null) rv = entry.getValue(); assert rv != null : "Found no node for hash " + hash; return rv; } class KetamaIterator implements Iterator<Node> { private HashRing<Long, Node> iteratorNodes; final String key; long hashVal; int remainingTries; int numTries = 0; public KetamaIterator(final HashRing<Long, Node> nodes, final String k, final int t) { super(); iteratorNodes = nodes; hashVal = hashAlg.hash(k); remainingTries = t; key = k; } private void nextHash() { hashVal = hashAlg.hash((numTries++) + key); remainingTries--; } public boolean hasNext() { return remainingTries > 0; } public Node next() { try { return getNodeForKey(iteratorNodes, hashVal); } finally { nextHash(); } } public void remove() { throw new UnsupportedOperationException("remove not supported"); } } }