/** * 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 org.apache.hadoop.hbase.master.balancer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.ClusterStatus; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.master.AssignmentManager; import org.apache.hadoop.hbase.master.LoadBalancer; import org.apache.hadoop.hbase.master.MasterServices; import com.google.common.base.Joiner; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Sets; /** * The base class for load balancers. It provides the the functions used to by * {@link AssignmentManager} to assign regions in the edge cases. It doesn't * provide an implementation of the actual balancing algorithm. * */ public abstract class BaseLoadBalancer implements LoadBalancer { // slop for regions 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 MasterServices services; @Override public void setConf(Configuration conf) { this.slop = conf.getFloat("hbase.regions.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(MasterServices 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 regions and all the servers in the cluster and * returns a map of each server to the regions 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 regions with their blocks * * @param regions all regions * @param servers all servers * @return map of server to the regions it should take, or null if no * assignment is possible (ie. no regions or no servers) */ public Map<ServerName, List<HRegionInfo>> roundRobinAssignment(List<HRegionInfo> regions, List<ServerName> servers) { if (regions.isEmpty() || servers.isEmpty()) { return null; } Map<ServerName, List<HRegionInfo>> assignments = new TreeMap<ServerName, List<HRegionInfo>>(); int numRegions = regions.size(); int numServers = servers.size(); int max = (int) Math.ceil((float) numRegions / numServers); int serverIdx = 0; if (numServers > 1) { serverIdx = RANDOM.nextInt(numServers); } int regionIdx = 0; for (int j = 0; j < numServers; j++) { ServerName server = servers.get((j + serverIdx) % numServers); List<HRegionInfo> serverRegions = new ArrayList<HRegionInfo>(max); for (int i = regionIdx; i < numRegions; i += numServers) { serverRegions.add(regions.get(i % numRegions)); } assignments.put(server, serverRegions); regionIdx++; } return assignments; } /** * Generates an immediate assignment plan to be used by a new master for * regions in transition that do not have an already known destination. * * Takes a list of regions that need immediate assignment and a list of all * available servers. Returns a map of regions 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 regions * @param servers * @return map of regions to the server it should be assigned to */ public Map<HRegionInfo, ServerName> immediateAssignment(List<HRegionInfo> regions, List<ServerName> servers) { Map<HRegionInfo, ServerName> assignments = new TreeMap<HRegionInfo, ServerName>(); for (HRegionInfo region : regions) { assignments.put(region, randomAssignment(region, servers)); } return assignments; } /** * Used to assign a single region to a random server. */ public ServerName randomAssignment(HRegionInfo regionInfo, 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 regions to their existing assignment from META. Also * takes a list of online servers for regions to be assigned to. Attempts to * retain all assignment, so in some instances initial assignment will not be * completely balanced. * <p> * Any leftover regions without an existing server to be assigned to will be * assigned randomly to available servers. * * @param regions regions and existing assignment from meta * @param servers available servers * @return map of servers and regions to be assigned to them */ public Map<ServerName, List<HRegionInfo>> retainAssignment(Map<HRegionInfo, ServerName> regions, 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<HRegionInfo>> assignments = new TreeMap<ServerName, List<HRegionInfo>>(); for (ServerName server : servers) { assignments.put(server, new ArrayList<HRegionInfo>()); } // Collection of the hostnames that used to have regions // 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<HRegionInfo, ServerName> entry : regions.entrySet()) { HRegionInfo region = 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(region); 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(region); 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(region); numRetainedAssigments++; } } String randomAssignMsg = ""; if (numRandomAssignments > 0) { randomAssignMsg = numRandomAssignments + " regions were assigned " + "to random hosts, since the old hosts for these regions are no " + "longer present in the cluster. These hosts were:\n " + Joiner.on("\n ").join(oldHostsNoLongerPresent); } LOG.info("Reassigned " + regions.size() + " regions. " + numRetainedAssigments + " retained the pre-restart assignment. " + randomAssignMsg); return assignments; } }