/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.dataplacement; import org.infinispan.commons.hash.Hash; import org.infinispan.distribution.DistributionManager; import org.infinispan.distribution.ch.ConsistentHash; import org.infinispan.distribution.ch.DataPlacementConsistentHash; import org.infinispan.remoting.transport.Address; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; /** * Collects all the remote and local access for each member for the key in which this member is the * primary owner * * @author Zhongmiao Li * @author João Paiva * @author Pedro Ruivo * @since 5.2 */ public class ObjectPlacementManager { private static final Log log = LogFactory.getLog(ObjectPlacementManager.class); private ClusterSnapshot clusterSnapshot; private ObjectRequest[] objectRequests; private final BitSet requestReceived; //this can be quite big. save it as an array to save some memory private Object[] allKeysMoved; private final DistributionManager distributionManager; private final Hash hash; private final int defaultNumberOfOwners; public ObjectPlacementManager(DistributionManager distributionManager, Hash hash, int defaultNumberOfOwners){ this.distributionManager = distributionManager; this.hash = hash; this.defaultNumberOfOwners = defaultNumberOfOwners; requestReceived = new BitSet(); allKeysMoved = new Object[0]; } /** * reset the state (before each round) * * @param roundClusterSnapshot the current cluster members */ public final synchronized void resetState(ClusterSnapshot roundClusterSnapshot) { clusterSnapshot = roundClusterSnapshot; objectRequests = new ObjectRequest[clusterSnapshot.size()]; requestReceived.clear(); } /** * collects the local and remote accesses for each member * * @param member the member that sent the {@code objectRequest} * @param objectRequest the local and remote accesses * @return true if all requests are received, false otherwise. It only returns true on the first * time it has all the objects */ public final synchronized boolean aggregateRequest(Address member, ObjectRequest objectRequest) { if (hasReceivedAllRequests()) { return false; } int senderIdx = clusterSnapshot.indexOf(member); if (senderIdx < 0) { log.warnf("Received request list from %s but it does not exits in %s", member, clusterSnapshot); return false; } objectRequests[senderIdx] = objectRequest; requestReceived.set(senderIdx); logRequestReceived(member, objectRequest); return hasReceivedAllRequests(); } /** * calculate the new owners based on the requests received. * * @return a map with the keys to be moved and the new owners */ public final synchronized Map<Object, OwnersInfo> calculateObjectsToMove() { Map<Object, OwnersInfo> newOwnersMap = new HashMap<Object, OwnersInfo>(); for (int requesterIdx = 0; requesterIdx < clusterSnapshot.size(); ++requesterIdx) { ObjectRequest objectRequest = objectRequests[requesterIdx]; if (objectRequest == null) { continue; } Map<Object, Long> requestedObjects = objectRequest.getRemoteAccesses(); for (Map.Entry<Object, Long> entry : requestedObjects.entrySet()) { calculateNewOwners(newOwnersMap, entry.getKey(), entry.getValue(), requesterIdx); } //release memory asap requestedObjects.clear(); } removeNotMovedObjects(newOwnersMap); //process the old moved keys. this will set the new owners of the previous rounds for (Object key : allKeysMoved) { if (!newOwnersMap.containsKey(key)) { newOwnersMap.put(key, createOwnersInfo(key)); } } //update all the keys moved array allKeysMoved = newOwnersMap.keySet().toArray(new Object[newOwnersMap.size()]); return newOwnersMap; } /** * returns all keys moved so far * * @return all keys moved so far */ public final Collection<Object> getKeysToMove() { return Arrays.asList(allKeysMoved); } /** * for each object to move, it checks if the owners are different from the owners returned by the original * Infinispan's consistent hash. If this is true, the object is removed from the map {@code newOwnersMap} * * @param newOwnersMap the map with the key to be moved and the new owners */ private void removeNotMovedObjects(Map<Object, OwnersInfo> newOwnersMap) { ConsistentHash defaultConsistentHash = getDefaultConsistentHash(); Iterator<Map.Entry<Object, OwnersInfo>> iterator = newOwnersMap.entrySet().iterator(); //if the owners info corresponds to the default consistent hash owners, remove the key from the map mainLoop: while (iterator.hasNext()) { Map.Entry<Object, OwnersInfo> entry = iterator.next(); Object key = entry.getKey(); OwnersInfo ownersInfo = entry.getValue(); Collection<Integer> ownerInfoIndexes = ownersInfo.getNewOwnersIndexes(); Collection<Address> defaultOwners = defaultConsistentHash.locate(key, defaultNumberOfOwners); if (ownerInfoIndexes.size() != defaultOwners.size()) { continue; } for (Address address : defaultOwners) { if (!ownerInfoIndexes.contains(clusterSnapshot.indexOf(address))) { continue mainLoop; } } iterator.remove(); } } /** * updates the owner information for the {@code key} based in the {@code numberOfRequests} made by the member who * requested this {@code key} (identified by {@code requesterId}) * * @param newOwnersMap the new owners map to be updated * @param key the key requested * @param numberOfRequests the number of accesses made to this key * @param requesterId the member id */ private void calculateNewOwners(Map<Object, OwnersInfo> newOwnersMap, Object key, long numberOfRequests, int requesterId) { OwnersInfo newOwnersInfo = newOwnersMap.get(key); if (newOwnersInfo == null) { newOwnersInfo = createOwnersInfo(key); newOwnersMap.put(key, newOwnersInfo); } newOwnersInfo.calculateNewOwner(requesterId, numberOfRequests); } /** * returns the local accesses and owners for the {@code key} * * @param key the key * @return the local accesses and owners for the key */ private Map<Integer, Long> getLocalAccesses(Object key) { Map<Integer, Long> localAccessesMap = new TreeMap<Integer, Long>(); for (int memberIndex = 0; memberIndex < objectRequests.length; ++memberIndex) { ObjectRequest request = objectRequests[memberIndex]; if (request == null) { continue; } Long localAccesses = request.getLocalAccesses().remove(key); if (localAccesses != null) { localAccessesMap.put(memberIndex, localAccesses); } } return localAccessesMap; } /** * creates a new owners information initialized with the current owners returned by the current consistent hash * and their number of accesses for the {@code key} * * @param key the key * @return the new owners information. */ private OwnersInfo createOwnersInfo(Object key) { Collection<Address> replicas = distributionManager.locate(key); Map<Integer, Long> localAccesses = getLocalAccesses(key); OwnersInfo ownersInfo = new OwnersInfo(replicas.size()); for (Address currentOwner : replicas) { int ownerIndex = clusterSnapshot.indexOf(currentOwner); if (ownerIndex == -1) { ownerIndex = findNewOwner(key, replicas); } Long accesses = localAccesses.remove(ownerIndex); if (accesses == null) { //TODO check if this should be zero or the min number of local accesses from the member accesses = 0L; } ownersInfo.add(ownerIndex, accesses); } return ownersInfo; } /** * finds the new owner for the {@code key} based on the Infinispan's consistent hash. this is invoked * when the one or more current owners are not in the cluster anymore and it is necessary to find new owners * to respect the default number of owners per key * * @param key the key * @param alreadyOwner the current owners * @return the new owner index */ private int findNewOwner(Object key, Collection<Address> alreadyOwner) { int size = clusterSnapshot.size(); if (size <= 1) { return 0; } int startIndex = hash.hash(key) % size; for (int index = startIndex + 1; index != startIndex; index = (index + 1) % size) { if (!alreadyOwner.contains(clusterSnapshot.get(index))) { return index; } index = (index + 1) % size; } return 0; } /** * returns the actual consistent hashing * * @return the actual consistent hashing */ private ConsistentHash getDefaultConsistentHash() { ConsistentHash hash = this.distributionManager.getConsistentHash(); return hash instanceof DataPlacementConsistentHash ? ((DataPlacementConsistentHash) hash).getDefaultHash() : hash; } private boolean hasReceivedAllRequests() { return requestReceived.cardinality() == clusterSnapshot.size(); } private void logRequestReceived(Address sender, ObjectRequest request) { if (log.isTraceEnabled()) { StringBuilder missingMembers = new StringBuilder(); for (int i = 0; i < clusterSnapshot.size(); ++i) { if (!requestReceived.get(i)) { missingMembers.append(clusterSnapshot.get(i)).append(" "); } } log.debugf("Object Request received from %s. Missing request are %s. The Object Request is %s", sender, missingMembers, request.toString(true)); } else if (log.isDebugEnabled()) { log.debugf("Object Request received from %s. Missing request are %s. The Object Request is %s", sender, (clusterSnapshot.size() - requestReceived.cardinality()), request.toString()); } } }