All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dasasian.chok.master.LowestShardCountDistributionPolicy Maven / Gradle / Ivy

There is a newer version: 1.7
Show newest version
/**
 * Copyright (C) 2014 Dasasian ([email protected])
 *
 * 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.
 */
/**
 j * Copyright 2008 the original author or authors.
 *
 * 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 com.dasasian.chok.master;

import org.apache.log4j.Logger;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * This code attempts to select the node that has the fewest shards, to deploy
 * replicate shards to, and to select the node that has the most shards to
 * remove excess replicas from.
 *
 * @deprecated DefaultDistributionPolicy already contains the same functionality
 * since version 6.1
 */
public class LowestShardCountDistributionPolicy implements IDeployPolicy {

    private final static Logger LOG = Logger.getLogger(LowestShardCountDistributionPolicy.class);

    Comparator> aliveComparator = new Comparator>() {

        @Override
        public int compare(Entry o1, Entry o2) {
            return o2.getValue().get() - o1.getValue().get();
        }

    };

    private TreeSet produceOrdedNodeList(final Map> currentNode2ShardsMap, List aliveNodes) {

        HashMap nodeShardCountsMap = new HashMap<>();
        for (String node : aliveNodes) {
            nodeShardCountsMap.put(node, new AtomicInteger(0));
        }
        for (Map.Entry> shardInfo : currentNode2ShardsMap.entrySet()) {
            if (LOG.isDebugEnabled()) {
                LOG.debug(String.format("Working on shard: %s, with list %s", shardInfo.getKey(), shardInfo.getValue()));
            }

            final List shards = shardInfo.getValue();
            if (shards == null || shards.isEmpty()) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug(String.format("empty node %s", shardInfo.getKey()));
                }
                continue;
            }

            AtomicInteger b = nodeShardCountsMap.get(shardInfo.getKey());
            if (b == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug(String.format("Skipping node %s, not in live list, carries shards %s", shardInfo.getKey(), shardInfo.getValue()));
                }
                continue; // node is not alive
            }

            b.addAndGet(shards.size());
        }
        TreeSet sortedAliveNodes = new TreeSet<>();
        for (Map.Entry bucket : nodeShardCountsMap.entrySet()) {
            sortedAliveNodes.add(new OrderedBucket(bucket));
        }

        return sortedAliveNodes;
    }

    public Map> createDistributionPlan(final Map> currentShard2NodesMap, final Map> currentNode2ShardsMap, List aliveNodes, final int replicationLevel) {
        if (aliveNodes.size() == 0) {
            throw new IllegalArgumentException("no alive nodes to distribute to");
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug(String.format("Live nodes are %d, %s", aliveNodes.size(), aliveNodes));
            LOG.debug(String.format("Current Shard 2 Nodes Map, %s", currentShard2NodesMap));
            LOG.debug(String.format("Current Nodes 2 Shard Map, %s", currentNode2ShardsMap));
        }

        Set shards = currentShard2NodesMap.keySet();
        TreeSet orderedNodes = produceOrdedNodeList(currentNode2ShardsMap, aliveNodes);

        for (String shard : shards) {
            Set assignedNodes = new HashSet<>(replicationLevel);
            int neededDeployments = replicationLevel - countValues(currentShard2NodesMap, shard);
            assignedNodes.addAll(currentShard2NodesMap.get(shard));

            // now assign new nodes based on round robin algorithm
            neededDeployments = chooseNewNodes(currentNode2ShardsMap, orderedNodes, shard, assignedNodes, neededDeployments);

            if (neededDeployments > 0) {
                LOG.warn("cannot replicate shard '" + shard + "' " + replicationLevel + " times, cause only " + orderedNodes.size() + " nodes connected");
            } else if (neededDeployments < 0) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("found shard '" + shard + "' over-replicated");
                }
                // TODO jz: maybe we should add a configurable threshold tha e.g. 10%
                // over replication is ok ?
                removeOverreplicatedShards(currentShard2NodesMap, currentNode2ShardsMap, orderedNodes, shard, neededDeployments);
            }
        }
        return currentNode2ShardsMap;
    }

    /**
     * Place a shard into the left most element or ordered nodes that can hold
     * them. orderedNodes is modified, as is currentNode2ShardsMap, which gets the
     * additional shard added to the selected node.
     */
    private int chooseNewNodes(final Map> currentNode2ShardsMap, TreeSet orderedNodes, String shard, Set assignedNodes, int neededDeployments) {

        String currentNode;
        ArrayList toAddBack = new ArrayList<>();
        try {
            for (Iterator ordered = orderedNodes.iterator(); neededDeployments > 0 && ordered.hasNext(); ) {
                final OrderedBucket nodeShards = ordered.next();
                currentNode = nodeShards.getKey();

                // This is the lowest weight node, so this shard is either going here,
                // or already present
                toAddBack.add(nodeShards); // remove form the working list, to save
                // later loop work, and add it to the add
                // back list
                ordered.remove();

                final boolean nodeContainsShard = assignedNodes.contains(currentNode);
                if (LOG.isDebugEnabled()) {
                    LOG.debug(String.format("chooseNewNodes: for shard %s, Current test node is %s, shardCount %d, %s", shard, currentNode, nodeShards.getValue().get(), nodeContainsShard ? "contains shard" : "can take shard"));
                }

                if (nodeContainsShard) {
                    continue;
                }
                currentNode2ShardsMap.get(currentNode).add(shard);
                assignedNodes.add(currentNode);
                nodeShards.getValue().incrementAndGet(); // Increment the count
                neededDeployments--;
            }
        } finally { // Put any nodes that were removed form the ordered list back,
            // this automatically re-orders the list as well
            orderedNodes.addAll(toAddBack);
        }
        return neededDeployments;
    }

    /**
     * This could be modified to use the orderedNodes, but it works well enough
     * for the small data set sizes
     */

    private void removeOverreplicatedShards(final Map> currentShard2NodesMap, final Map> currentNode2ShardsMap, TreeSet orderedNodes, String shard, int neededDeployments) {
        Iterator ordered = orderedNodes.descendingIterator();

        ArrayList toAddBack = new ArrayList<>();
        try {

            while (neededDeployments < 0 && ordered.hasNext()) {
                final OrderedBucket nodeShards = ordered.next();

                String maxShardServingNode = null;
                List nodeNames = currentShard2NodesMap.get(shard);
                HashSet nodeNameMap = new HashSet<>(nodeNames);

                toAddBack.add(nodeShards); // remove form the working list, to save
                // later loop work, and add it to the add
                // back list
                ordered.remove();

                final String testNode = nodeShards.getKey();

                if (!nodeNameMap.contains(testNode)) {
                    continue; // doesn't contain one of our items, remove from the working
                    // set and try the next most loaded node
                }
                // This node contains our shard and is the most loaded (by shard count)
                if (LOG.isDebugEnabled()) {
                    LOG.debug("remove " + shard + " from " + maxShardServingNode);
                }
                currentNode2ShardsMap.get(testNode).remove(shard);
                nodeShards.getValue().decrementAndGet(); // change the count
                neededDeployments++;
            }
        } finally {
            orderedNodes.addAll(toAddBack);
        }
    }

    private int countValues(Map> multiMap, String key) {
        List list = multiMap.get(key);
        if (list == null) {
            return 0;
        }
        return list.size();
    }

    static class OrderedBucket implements Comparable {
        final Map.Entry bucket;

        public OrderedBucket(final Map.Entry bucket) {
            this.bucket = bucket;
        }

        public String getKey() {
            return bucket.getKey();
        }

        public AtomicInteger getValue() {
            return bucket.getValue();
        }

        @Override
        public int compareTo(OrderedBucket o2) {
            if (o2 == this) {
                return 0;
            }
            // sort null entries to the end
            if (bucket == null) {
                return 1;
            }
            if (o2.bucket == null) {
                return -1;
            }
            int res = getValue().get() - o2.getValue().get();
            if (res != 0) {
                return res;
            }
            res = o2.getKey().compareTo(getKey());
            if (res != 0) {
                return res;
            }
            return o2.hashCode() - hashCode();
        }

        public String toString() {
            if (bucket == null) {
                return "null";
            }
            return "[ " + bucket.getKey() + '=' + bucket.getValue().get() + "] ";
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy