com.alibaba.wasp.master.balancer.BaseLoadBalancer.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.wasp.master.balancer.BaseLoadBalancer.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.alibaba.wasp.master.balancer;

import com.alibaba.wasp.ClusterStatus;
import com.alibaba.wasp.EntityGroupInfo;
import com.alibaba.wasp.ServerName;
import com.alibaba.wasp.master.FMasterServices;
import com.alibaba.wasp.master.LoadBalancer;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;

/**
 * The base class for load balancers. It provides the the functions used to by
 * {@link EntityGroupManager} to assign entityGroups in the edge cases. It
 * doesn't provide an implementation of the actual balancing algorithm.
 * 
 */
public abstract class BaseLoadBalancer implements LoadBalancer {

    // slop for entityGroups
    private float slop;
    private Configuration config;
    private static final Random RANDOM = new Random(System.currentTimeMillis());
    private static final Log LOG = LogFactory.getLog(BaseLoadBalancer.class);

    protected FMasterServices services;

    @Override
    public void setConf(Configuration conf) {
        this.slop = conf.getFloat("hbase.entityGroups.slop", (float) 0.2);
        if (slop < 0)
            slop = 0;
        else if (slop > 1)
            slop = 1;
        this.config = conf;
    }

    @Override
    public Configuration getConf() {
        return this.config;
    }

    public void setClusterStatus(ClusterStatus st) {
        // Not used except for the StocasticBalancer
    }

    public void setMasterServices(FMasterServices masterServices) {
        this.services = masterServices;
    }

    protected boolean needsBalance(ClusterLoadState cs) {
        // Check if we even need to do any load balancing
        float average = cs.getLoadAverage(); // for logging
        // HBASE-3681 check sloppiness first
        int floor = (int) Math.floor(average * (1 - slop));
        int ceiling = (int) Math.ceil(average * (1 + slop));

        return cs.getMinLoad() > ceiling || cs.getMaxLoad() < floor;
    }

    /**
     * Generates a bulk assignment plan to be used on cluster startup using a
     * simple round-robin assignment.
     * <p>
     * Takes a list of all the entityGroups and all the servers in the cluster and
     * returns a map of each server to the entityGroups that it should be assigned.
     * <p>
     * Currently implemented as a round-robin assignment. Same invariant as load
     * balancing, all servers holding floor(avg) or ceiling(avg).
     * 
     * TODO: Use block locations from HDFS to place entityGroups with their blocks
     * 
     * @param entityGroups all entityGroups
     * @param servers all servers
     * @return map of server to the entityGroups it should take, or null if no
     *         assignment is possible (ie. no entityGroups or no servers)
     */
    public Map<ServerName, List<EntityGroupInfo>> roundRobinAssignment(List<EntityGroupInfo> entityGroups,
            List<ServerName> servers) {
        if (entityGroups.isEmpty() || servers.isEmpty()) {
            return null;
        }
        Map<ServerName, List<EntityGroupInfo>> assignments = new TreeMap<ServerName, List<EntityGroupInfo>>();
        int numEntityGroups = entityGroups.size();
        int numServers = servers.size();
        int max = (int) Math.ceil((float) numEntityGroups / numServers);
        int serverIdx = 0;
        if (numServers > 1) {
            serverIdx = RANDOM.nextInt(numServers);
        }
        int entityGroupIdx = 0;
        for (int j = 0; j < numServers; j++) {
            ServerName server = servers.get((j + serverIdx) % numServers);
            List<EntityGroupInfo> serverEntityGroups = new ArrayList<EntityGroupInfo>(max);
            for (int i = entityGroupIdx; i < numEntityGroups; i += numServers) {
                serverEntityGroups.add(entityGroups.get(i % numEntityGroups));
            }
            assignments.put(server, serverEntityGroups);
            entityGroupIdx++;
        }
        return assignments;
    }

    /**
     * Generates an immediate assignment plan to be used by a new master for
     * entityGroups in transition that do not have an already known destination.
     * 
     * Takes a list of entityGroups that need immediate assignment and a list of all
     * available servers. Returns a map of entityGroups to the server they should be
     * assigned to.
     * 
     * This method will return quickly and does not do any intelligent balancing.
     * The goal is to make a fast decision not the best decision possible.
     * 
     * Currently this is random.
     * 
     * @param entityGroups
     * @param servers
     * @return map of entityGroups to the server it should be assigned to
     */
    public Map<EntityGroupInfo, ServerName> immediateAssignment(List<EntityGroupInfo> entityGroups,
            List<ServerName> servers) {
        Map<EntityGroupInfo, ServerName> assignments = new TreeMap<EntityGroupInfo, ServerName>();
        for (EntityGroupInfo entityGroup : entityGroups) {
            assignments.put(entityGroup, randomAssignment(entityGroup, servers));
        }
        return assignments;
    }

    /**
     * Used to assign a single entityGroup to a random server.
     */
    public ServerName randomAssignment(EntityGroupInfo entityGroupInfo, List<ServerName> servers) {
        if (servers == null || servers.isEmpty()) {
            LOG.warn("Wanted to do random assignment but no servers to assign to");
            return null;
        }
        return servers.get(RANDOM.nextInt(servers.size()));
    }

    /**
     * Generates a bulk assignment startup plan, attempting to reuse the existing
     * assignment information from META, but adjusting for the specified list of
     * available/online servers available for assignment.
     * <p>
     * Takes a map of all entityGroups to their existing assignment from META. Also
     * takes a list of online servers for entityGroups to be assigned to. Attempts to
     * retain all assignment, so in some instances initial assignment will not be
     * completely balanced.
     * <p>
     * Any leftover entityGroups without an existing server to be assigned to will be
     * assigned randomly to available servers.
     * 
     * @param entityGroups entityGroups and existing assignment from meta
     * @param servers available servers
     * @return map of servers and entityGroups to be assigned to them
     */
    public Map<ServerName, List<EntityGroupInfo>> retainAssignment(Map<EntityGroupInfo, ServerName> entityGroups,
            List<ServerName> servers) {
        // Group all of the old assignments by their hostname.
        // We can't group directly by ServerName since the servers all have
        // new start-codes.

        // Group the servers by their hostname. It's possible we have multiple
        // servers on the same host on different ports.
        ArrayListMultimap<String, ServerName> serversByHostname = ArrayListMultimap.create();
        for (ServerName server : servers) {
            serversByHostname.put(server.getHostname(), server);
        }

        // Now come up with new assignments
        Map<ServerName, List<EntityGroupInfo>> assignments = new TreeMap<ServerName, List<EntityGroupInfo>>();

        for (ServerName server : servers) {
            assignments.put(server, new ArrayList<EntityGroupInfo>());
        }

        // Collection of the hostnames that used to have entityGroups
        // assigned, but for which we no longer have any RS running
        // after the cluster restart.
        Set<String> oldHostsNoLongerPresent = Sets.newTreeSet();

        int numRandomAssignments = 0;
        int numRetainedAssigments = 0;
        for (Map.Entry<EntityGroupInfo, ServerName> entry : entityGroups.entrySet()) {
            EntityGroupInfo entityGroup = entry.getKey();
            ServerName oldServerName = entry.getValue();
            List<ServerName> localServers = new ArrayList<ServerName>();
            if (oldServerName != null) {
                localServers = serversByHostname.get(oldServerName.getHostname());
            }
            if (localServers.isEmpty()) {
                // No servers on the new cluster match up with this hostname,
                // assign randomly.
                ServerName randomServer = servers.get(RANDOM.nextInt(servers.size()));
                assignments.get(randomServer).add(entityGroup);
                numRandomAssignments++;
                if (oldServerName != null)
                    oldHostsNoLongerPresent.add(oldServerName.getHostname());
            } else if (localServers.size() == 1) {
                // the usual case - one new server on same host
                assignments.get(localServers.get(0)).add(entityGroup);
                numRetainedAssigments++;
            } else {
                // multiple new servers in the cluster on this same host
                int size = localServers.size();
                ServerName target = localServers.get(RANDOM.nextInt(size));
                assignments.get(target).add(entityGroup);
                numRetainedAssigments++;
            }
        }

        String randomAssignMsg = "";
        if (numRandomAssignments > 0) {
            randomAssignMsg = numRandomAssignments + " entityGroups were assigned "
                    + "to random hosts, since the old hosts for these entityGroups are no "
                    + "longer present in the cluster. These hosts were:\n  "
                    + Joiner.on("\n  ").join(oldHostsNoLongerPresent);
        }

        LOG.info("Reassigned " + entityGroups.size() + " entityGroups. " + numRetainedAssigments
                + " retained the pre-restart assignment. " + randomAssignMsg);
        return assignments;
    }

}