View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.master.balancer;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Random;
24  import java.util.Set;
25  import java.util.TreeMap;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.ClusterStatus;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.ServerName;
33  import org.apache.hadoop.hbase.master.AssignmentManager;
34  import org.apache.hadoop.hbase.master.LoadBalancer;
35  import org.apache.hadoop.hbase.master.MasterServices;
36  
37  import com.google.common.base.Joiner;
38  import com.google.common.collect.ArrayListMultimap;
39  import com.google.common.collect.Sets;
40  
41  /**
42   * The base class for load balancers. It provides the the functions used to by
43   * {@link AssignmentManager} to assign regions in the edge cases. It doesn't
44   * provide an implementation of the actual balancing algorithm.
45   *
46   */
47  public abstract class BaseLoadBalancer implements LoadBalancer {
48  
49    // slop for regions
50    private float slop;
51    private Configuration config;
52    private static final Random RANDOM = new Random(System.currentTimeMillis());
53    private static final Log LOG = LogFactory.getLog(BaseLoadBalancer.class);
54  
55    protected MasterServices services;
56  
57    @Override
58    public void setConf(Configuration conf) {
59      this.slop = conf.getFloat("hbase.regions.slop", (float) 0.2);
60      if (slop < 0) slop = 0;
61      else if (slop > 1) slop = 1;
62      this.config = conf;
63    }
64  
65    @Override
66    public Configuration getConf() {
67      return this.config;
68    }
69  
70    public void setClusterStatus(ClusterStatus st) {
71      // Not used except for the StocasticBalancer
72    }
73  
74    public void setMasterServices(MasterServices masterServices) {
75      this.services = masterServices;
76    }
77  
78    protected boolean needsBalance(ClusterLoadState cs) {
79      // Check if we even need to do any load balancing
80      float average = cs.getLoadAverage(); // for logging
81      // HBASE-3681 check sloppiness first
82      int floor = (int) Math.floor(average * (1 - slop));
83      int ceiling = (int) Math.ceil(average * (1 + slop));
84  
85      return cs.getMinLoad() > ceiling || cs.getMaxLoad() < floor;
86    }
87  
88    /**
89     * Generates a bulk assignment plan to be used on cluster startup using a
90     * simple round-robin assignment.
91     * <p>
92     * Takes a list of all the regions and all the servers in the cluster and
93     * returns a map of each server to the regions that it should be assigned.
94     * <p>
95     * Currently implemented as a round-robin assignment. Same invariant as load
96     * balancing, all servers holding floor(avg) or ceiling(avg).
97     *
98     * TODO: Use block locations from HDFS to place regions with their blocks
99     *
100    * @param regions all regions
101    * @param servers all servers
102    * @return map of server to the regions it should take, or null if no
103    *         assignment is possible (ie. no regions or no servers)
104    */
105   public Map<ServerName, List<HRegionInfo>> roundRobinAssignment(List<HRegionInfo> regions,
106       List<ServerName> servers) {
107     if (regions.isEmpty() || servers.isEmpty()) {
108       return null;
109     }
110     Map<ServerName, List<HRegionInfo>> assignments = new TreeMap<ServerName, List<HRegionInfo>>();
111     int numRegions = regions.size();
112     int numServers = servers.size();
113     int max = (int) Math.ceil((float) numRegions / numServers);
114     int serverIdx = 0;
115     if (numServers > 1) {
116       serverIdx = RANDOM.nextInt(numServers);
117     }
118     int regionIdx = 0;
119     for (int j = 0; j < numServers; j++) {
120       ServerName server = servers.get((j + serverIdx) % numServers);
121       List<HRegionInfo> serverRegions = new ArrayList<HRegionInfo>(max);
122       for (int i = regionIdx; i < numRegions; i += numServers) {
123         serverRegions.add(regions.get(i % numRegions));
124       }
125       assignments.put(server, serverRegions);
126       regionIdx++;
127     }
128     return assignments;
129   }
130 
131   /**
132    * Generates an immediate assignment plan to be used by a new master for
133    * regions in transition that do not have an already known destination.
134    *
135    * Takes a list of regions that need immediate assignment and a list of all
136    * available servers. Returns a map of regions to the server they should be
137    * assigned to.
138    *
139    * This method will return quickly and does not do any intelligent balancing.
140    * The goal is to make a fast decision not the best decision possible.
141    *
142    * Currently this is random.
143    *
144    * @param regions
145    * @param servers
146    * @return map of regions to the server it should be assigned to
147    */
148   public Map<HRegionInfo, ServerName> immediateAssignment(List<HRegionInfo> regions,
149       List<ServerName> servers) {
150     Map<HRegionInfo, ServerName> assignments = new TreeMap<HRegionInfo, ServerName>();
151     for (HRegionInfo region : regions) {
152       assignments.put(region, randomAssignment(region, servers));
153     }
154     return assignments;
155   }
156 
157   /**
158    * Used to assign a single region to a random server.
159    */
160   public ServerName randomAssignment(HRegionInfo regionInfo, List<ServerName> servers) {
161     if (servers == null || servers.isEmpty()) {
162       LOG.warn("Wanted to do random assignment but no servers to assign to");
163       return null;
164     }
165     return servers.get(RANDOM.nextInt(servers.size()));
166   }
167 
168   /**
169    * Generates a bulk assignment startup plan, attempting to reuse the existing
170    * assignment information from META, but adjusting for the specified list of
171    * available/online servers available for assignment.
172    * <p>
173    * Takes a map of all regions to their existing assignment from META. Also
174    * takes a list of online servers for regions to be assigned to. Attempts to
175    * retain all assignment, so in some instances initial assignment will not be
176    * completely balanced.
177    * <p>
178    * Any leftover regions without an existing server to be assigned to will be
179    * assigned randomly to available servers.
180    *
181    * @param regions regions and existing assignment from meta
182    * @param servers available servers
183    * @return map of servers and regions to be assigned to them
184    */
185   public Map<ServerName, List<HRegionInfo>> retainAssignment(Map<HRegionInfo, ServerName> regions,
186       List<ServerName> servers) {
187     // Group all of the old assignments by their hostname.
188     // We can't group directly by ServerName since the servers all have
189     // new start-codes.
190 
191     // Group the servers by their hostname. It's possible we have multiple
192     // servers on the same host on different ports.
193     ArrayListMultimap<String, ServerName> serversByHostname = ArrayListMultimap.create();
194     for (ServerName server : servers) {
195       serversByHostname.put(server.getHostname(), server);
196     }
197 
198     // Now come up with new assignments
199     Map<ServerName, List<HRegionInfo>> assignments = new TreeMap<ServerName, List<HRegionInfo>>();
200 
201     for (ServerName server : servers) {
202       assignments.put(server, new ArrayList<HRegionInfo>());
203     }
204 
205     // Collection of the hostnames that used to have regions
206     // assigned, but for which we no longer have any RS running
207     // after the cluster restart.
208     Set<String> oldHostsNoLongerPresent = Sets.newTreeSet();
209 
210     int numRandomAssignments = 0;
211     int numRetainedAssigments = 0;
212     for (Map.Entry<HRegionInfo, ServerName> entry : regions.entrySet()) {
213       HRegionInfo region = entry.getKey();
214       ServerName oldServerName = entry.getValue();
215       List<ServerName> localServers = new ArrayList<ServerName>();
216       if (oldServerName != null) {
217         localServers = serversByHostname.get(oldServerName.getHostname());
218       }
219       if (localServers.isEmpty()) {
220         // No servers on the new cluster match up with this hostname,
221         // assign randomly.
222         ServerName randomServer = servers.get(RANDOM.nextInt(servers.size()));
223         assignments.get(randomServer).add(region);
224         numRandomAssignments++;
225         if (oldServerName != null) oldHostsNoLongerPresent.add(oldServerName.getHostname());
226       } else if (localServers.size() == 1) {
227         // the usual case - one new server on same host
228         assignments.get(localServers.get(0)).add(region);
229         numRetainedAssigments++;
230       } else {
231         // multiple new servers in the cluster on this same host
232         int size = localServers.size();
233         ServerName target = localServers.get(RANDOM.nextInt(size));
234         assignments.get(target).add(region);
235         numRetainedAssigments++;
236       }
237     }
238 
239     String randomAssignMsg = "";
240     if (numRandomAssignments > 0) {
241       randomAssignMsg =
242           numRandomAssignments + " regions were assigned "
243               + "to random hosts, since the old hosts for these regions are no "
244               + "longer present in the cluster. These hosts were:\n  "
245               + Joiner.on("\n  ").join(oldHostsNoLongerPresent);
246     }
247 
248     LOG.info("Reassigned " + regions.size() + " regions. " + numRetainedAssigments
249         + " retained the pre-restart assignment. " + randomAssignMsg);
250     return assignments;
251   }
252 
253 }