/** *Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] *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 net.rubyeye.xmemcached.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; import net.rubyeye.xmemcached.HashAlgorithm; import net.rubyeye.xmemcached.impl.AbstractMemcachedSessionLocator; import com.google.code.yanf4j.core.Session; /** * Consistent Hash Algorithm implementation is compatible with libmemcached * method. * * @author dennis * */ public class LibmemcachedMemcachedSessionLocator extends AbstractMemcachedSessionLocator { static final int DEFAULT_NUM_REPS = 100; private transient volatile TreeMap<Long, List<Session>> ketamaSessions = new TreeMap<Long, List<Session>>(); private int maxTries; private int numReps = DEFAULT_NUM_REPS; private final Random random = new Random(); private HashAlgorithm hashAlgorithm = HashAlgorithm.ONE_AT_A_TIME; public LibmemcachedMemcachedSessionLocator() { } public LibmemcachedMemcachedSessionLocator(int numReps, HashAlgorithm hashAlgorithm) { super(); this.numReps = numReps; this.hashAlgorithm = hashAlgorithm; } private final void buildMap(Collection<Session> list, HashAlgorithm alg) { TreeMap<Long, List<Session>> sessionMap = new TreeMap<Long, List<Session>>(); for (Session session : list) { String sockStr = null; if (session.getRemoteSocketAddress().getPort() != 11211) { sockStr = session.getRemoteSocketAddress().getHostName() + ":" + session.getRemoteSocketAddress().getPort(); } else { sockStr = session.getRemoteSocketAddress().getHostName(); } for (int i = 0; i < this.numReps; i++) { long key = hashAlgorithm.hash(sockStr + "-" + i); this.getSessionList(sessionMap, key).add(session); } } this.ketamaSessions = sessionMap; this.maxTries = list.size(); } private List<Session> getSessionList( TreeMap<Long, List<Session>> sessionMap, long k) { List<Session> sessionList = sessionMap.get(k); if (sessionList == null) { sessionList = new ArrayList<Session>(); sessionMap.put(k, sessionList); } return sessionList; } public final Session getSessionByKey(final String key) { if (this.ketamaSessions == null || this.ketamaSessions.size() == 0) { return null; } long hash = hashAlgorithm.hash(key); Session rv = this.getSessionByHash(hash); int tries = 0; while (!this.failureMode && (rv == null || rv.isClosed()) && tries++ < this.maxTries) { hash = this.nextHash(hash, key, tries); rv = this.getSessionByHash(hash); } return rv; } public final Session getSessionByHash(final long hash) { TreeMap<Long, List<Session>> sessionMap = this.ketamaSessions; if (sessionMap.size() == 0) { return null; } Long resultHash = hash; if (!sessionMap.containsKey(hash)) { // Java 1.6 adds a ceilingKey method, but xmemcached is compatible // with jdk5,So use tailMap method to do this. SortedMap<Long, List<Session>> tailMap = sessionMap.tailMap(hash); if (tailMap.isEmpty()) { resultHash = sessionMap.firstKey(); } else { resultHash = tailMap.firstKey(); } } // // if (!sessionMap.containsKey(resultHash)) { // resultHash = sessionMap.ceilingKey(resultHash); // if (resultHash == null && sessionMap.size() > 0) { // resultHash = sessionMap.firstKey(); // } // } List<Session> sessionList = sessionMap.get(resultHash); if (sessionList == null || sessionList.size() == 0) { return null; } int size = sessionList.size(); return sessionList.get(this.random.nextInt(size)); } public final long nextHash(long hashVal, String key, int tries) { long tmpKey = hashAlgorithm.hash(tries + key); hashVal += (int) (tmpKey ^ tmpKey >>> 32); hashVal &= 0xffffffffL; /* truncate to 32-bits */ return hashVal; } public final void updateSessions(final Collection<Session> list) { this.buildMap(list, null); } }