/*
* Copyright 2014 University of Southern California
*
* 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 edu.usc.pgroup.floe.flake.messaging.dispersion.elasticmapreducer;
import edu.usc.pgroup.floe.app.Tuple;
import edu.usc.pgroup.floe.flake.FlakeToken;
import edu.usc.pgroup.floe.flake.ZKFlakeTokenCache;
import edu.usc.pgroup.floe.flake.messaging.dispersion.MessageDispersionStrategy;
import edu.usc.pgroup.floe.utils.Utils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.utils.ZKPaths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author kumbhare
*/
public class ElasticReducerDispersion extends MessageDispersionStrategy {
/**
* the global logger instance.
*/
private static final Logger LOGGER =
LoggerFactory.getLogger(ElasticReducerDispersion.class);
/**
* The hash ring for consistent hashing.
*/
private SortedMap<Integer, String> circle;
/**
* Reverse map which stores the mapping from the flakeid to its current
* value.
*/
private HashMap<String, Integer> flakeIdToTokenMap;
/**
* Key field name to be used for grouping.
*/
private String keyFieldName;
/**
* List of target pellet instances.
*/
private List<String> targetFlakeIds;
/**
* List of additional arguments to be sent.
*/
private List<String> flakeArgs;
/**
* Temp. storage to store mapping from flake id to hash value.
*/
private HashMap<String, Integer> actualHashes;
/**
* Hash function to be used.
*/
private HashingFunction hashingFunction;
/**
* Path cache to monitor the tokens.
*/
private ZKFlakeTokenCache flakeCache;
/**
* Default constructor.
*/
public ElasticReducerDispersion() {
this.targetFlakeIds = new ArrayList<>();
this.flakeArgs = new ArrayList<>();
this.circle = new TreeMap<>(Collections.reverseOrder());
this.flakeIdToTokenMap = new HashMap<>();
this.actualHashes = new HashMap<>();
this.hashingFunction = new Murmur32();
}
/**
* Returns the list of target instances to send the given tuple using the
* defined strategy.
* param tuple tuple object.
* return the list of target instances to send the given tuple.
*
* @param middleendreceiver middleend receiver to get the message.
* @param backend backend sender to send message to the succeeding flakes.
*
@Override
public final void disperseMessage(final ZMQ.Socket middleendreceiver,
final ZMQ.Socket backend) {
String key = middleendreceiver.recvStr(Charset.defaultCharset());
byte[] seralized = null;
if (key instanceof String) {
seralized = ((String) key).getBytes();
} else {
LOGGER.info("KEY IS NOT STRING. Use of string keys is suggested.");
seralized = Utils.serialize(key);
}
Integer actualHash = hashingFunction.hash(seralized);
Integer hash = getTargetFlakeHash(actualHash);
String fid = circle.get(hash);
LOGGER.debug("Sending to:{}", hash.toString());
backend.sendMore(fid.toString());
backend.sendMore(actualHash.toString());
backend.sendMore(String.valueOf(System.currentTimeMillis()));
Utils.forwardCompleteMessage(middleendreceiver, backend);
}*/
/**
* @param args the arguments sent by the user. Fix Me: make this a better
* interface.
*/
@Override
protected final void initialize(final String args) {
this.keyFieldName = args;
}
/**
* Returns the list of target instances to send the given tuple using the
* defined strategy.
* param tuple tuple object.
* @param tuple incoming tuple
* @return the list of target instances to send the given tuple
*/
@Override
public final List<String> getTargetFlakeIds(final Tuple tuple) {
Object key = tuple.get(keyFieldName);
byte[] seralized = null;
if (key instanceof String) {
seralized = ((String) key).getBytes();
} else {
LOGGER.info("KEY IS NOT STRING. Use of string keys is suggested.");
seralized = Utils.serialize(key);
}
Integer actualHash = hashingFunction.hash(seralized);
Integer hash = getTargetFlakeHash(actualHash);
String fid = circle.get(hash);
actualHashes.put(fid, actualHash);
targetFlakeIds.clear();
targetFlakeIds.add(fid);
return targetFlakeIds;
}
/**
* Should return a list of arguments/"envelopes" to be sent along with
* the message for the given target flake.
*
* @param flakeId one of the flake ids returned by getTargetFlakeIds
* @return list of arguments to be sent.
*/
@Override
public final List<String> getCustomArguments(final String flakeId) {
flakeArgs.clear();
flakeArgs.add(actualHashes.get(flakeId).toString());
flakeArgs.add(String.valueOf(System.currentTimeMillis()));
return flakeArgs;
}
/**
* Returns the list of target instances to send the given tuple using the
* defined strategy.
* @param actualHash the actual hash of the field.
* @return the list of target instances to send the given tuple.
*/
public final synchronized Integer getTargetFlakeHash(
final Integer actualHash) {
if (circle.isEmpty()) {
return null;
}
int hash = actualHash;
if (!circle.containsKey(hash)) {
SortedMap<Integer, String> tailMap = circle.tailMap(hash);
if (tailMap.isEmpty()) {
hash = circle.firstKey();
} else {
hash = tailMap.firstKey();
}
}
LOGGER.debug("Key:{}, actualHash:{}, token:{}, target:{}",
actualHash, hash, circle.get(hash));
return hash;
/** NOT REQUIRED............... SINCE FLAKES KNOW ABOUT THEIR
* NEIGHBOURS. EACH NEIGHBOUR CAN JUST AD AN EXTRA SUBSCRIPTION.
* THAT WAY WE CAN TAKE ADVANTAGE OF THE MULTI CAST PROTOCOL EASILY.
*/
//Add backups.. for PEER MESSAGE BACKUP
/*SortedMap<Integer, String> tail = circle.tailMap(hash);
Iterator<Integer> iterator = tail.keySet().iterator();
iterator.next(); //ignore the self's token.
int i = 0;
for (; i < replication && iterator.hasNext(); i++) {
Integer neighborToken = iterator.next();
targetFlakeIds.add(circle.get(neighborToken));
}
Iterator<Integer> frontIterator = circle.keySet().iterator();
for (; i < replication && frontIterator.hasNext(); i++) {
Integer neighborToken = frontIterator.next();
targetFlakeIds.add(circle.get(neighborToken));
}*/
}
/**
* Call back whenever a message is received from a target pellet instance
* on the back channel. This can be used by dispersion strategy to choose
* the target instance to send the message to.
* @param targetFlakeId flake id from which the
* message is received.
* @param message message body.
* @param toContinue true if the flake is sending a regular backchannel
* msg. False if the message is sent on scaling down i
* .e. 'terminate' is called on the target flake.
*/
@Override
public final void backChannelMessageReceived(final String targetFlakeId,
final byte[] message,
final Boolean toContinue) {
//Integer newPosition = (Integer) Utils.deserialize(message);
//updateCircle(targetFlakeId, newPosition, toContinue);
//LOGGER.debug("Circle: {}", circle);
}
/**
* Updates the circle.
* @param targetFlakeId flake id from which the message is received.
* @param newPosition position of the target flake on the ring.
* @param toContinue true if the flake is sending a regular backchannel
* msg. False if the message is sent on scaling down i
* .e. 'terminate' is called on the target flake.
*/
public final synchronized void updateCircle(final String targetFlakeId,
final Integer newPosition,
final boolean toContinue) {
Integer current = flakeIdToTokenMap.get(targetFlakeId);
if (current != null) {
circle.remove(current);
flakeIdToTokenMap.remove(targetFlakeId);
}
if (toContinue) {
LOGGER.info("received Token: {} for {}",
newPosition, targetFlakeId);
flakeIdToTokenMap.put(targetFlakeId, newPosition);
String prev = circle.get(newPosition);
if (prev != null) {
circle.remove(newPosition);
flakeIdToTokenMap.remove(prev);
}
circle.put(newPosition, targetFlakeId);
}
LOGGER.error("Circle:{}", circle);
}
/**
* Triggered when initial list of children is cached.
* This is retrieved synchronously.
*
* @param initialChildren initial list of children.
*/
@Override
public final void childrenListInitialized(
final Collection<ChildData> initialChildren) {
}
/**
* Triggered when a new child is added.
* Note: this is not recursive.
*
* @param addedChild newly added child's data.
*/
@Override
public final void childAdded(final ChildData addedChild) {
String destFid = ZKPaths.getNodeFromPath(addedChild.getPath());
LOGGER.error("Adding Dest FID: {}", destFid);
FlakeToken token = (FlakeToken) Utils.deserialize(
addedChild.getData());
updateCircle(destFid, token.getToken(), true);
}
/**
* Triggered when an existing child is removed.
* Note: this is not recursive.
*
* @param removedChild removed child's data.
*/
@Override
public final void childRemoved(final ChildData removedChild) {
String destFid = ZKPaths.getNodeFromPath(removedChild.getPath());
LOGGER.error("Removing dest FID: {}", destFid);
FlakeToken token = (FlakeToken) Utils.deserialize(
removedChild.getData());
updateCircle(destFid, token.getToken(), false);
}
/**
* Triggered when a child is updated.
* Note: This is called only when Children data is also cached in
* addition to stat information.
*
* @param updatedChild update child's data.
*/
@Override
public final void childUpdated(final ChildData updatedChild) {
String destFid = ZKPaths.getNodeFromPath(updatedChild.getPath());
LOGGER.error("Updating dest FID: {}", destFid);
FlakeToken token = (FlakeToken) Utils.deserialize(
updatedChild.getData());
updateCircle(destFid, token.getToken(), true);
}
}