001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.balancer;
019
020import edu.umd.cs.findbugs.annotations.NonNull;
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.NavigableMap;
029import java.util.Random;
030import java.util.Set;
031import java.util.TreeMap;
032import java.util.concurrent.ThreadLocalRandom;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.hbase.ClusterMetrics;
035import org.apache.hadoop.hbase.HBaseIOException;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.ServerName;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.master.LoadBalancer;
041import org.apache.hadoop.hbase.master.RackManager;
042import org.apache.hadoop.hbase.master.RegionPlan;
043import org.apache.yetus.audience.InterfaceAudience;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
048import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap;
049import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
050import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
051
052/**
053 * The base class for load balancers. It provides the functions used to by {@code AssignmentManager}
054 * to assign regions in the edge cases. It doesn't provide an implementation of the actual balancing
055 * algorithm.
056 * <p/>
057 * Since 3.0.0, all the balancers will be wrapped inside a {@code RSGroupBasedLoadBalancer}, it will
058 * be in charge of the synchronization of balancing and configuration changing, so we do not need to
059 * synchronize by ourselves.
060 */
061@InterfaceAudience.Private
062public abstract class BaseLoadBalancer implements LoadBalancer {
063
064  private static final Logger LOG = LoggerFactory.getLogger(BaseLoadBalancer.class);
065
066  public static final String BALANCER_DECISION_BUFFER_ENABLED =
067    "hbase.master.balancer.decision.buffer.enabled";
068  public static final boolean DEFAULT_BALANCER_DECISION_BUFFER_ENABLED = false;
069
070  public static final String BALANCER_REJECTION_BUFFER_ENABLED =
071    "hbase.master.balancer.rejection.buffer.enabled";
072  public static final boolean DEFAULT_BALANCER_REJECTION_BUFFER_ENABLED = false;
073
074  public static final boolean DEFAULT_HBASE_MASTER_LOADBALANCE_BYTABLE = false;
075
076  public static final String REGIONS_SLOP_KEY = "hbase.regions.slop";
077  public static final float REGIONS_SLOP_DEFAULT = 0.2f;
078
079  protected static final int MIN_SERVER_BALANCE = 2;
080  private volatile boolean stopped = false;
081
082  protected volatile RegionHDFSBlockLocationFinder regionFinder;
083  protected boolean useRegionFinder;
084  protected boolean isByTable = DEFAULT_HBASE_MASTER_LOADBALANCE_BYTABLE;
085
086  // slop for regions
087  protected float slop;
088  protected volatile RackManager rackManager;
089  protected MetricsBalancer metricsBalancer = null;
090  protected ClusterMetrics clusterStatus = null;
091  protected ServerName masterServerName;
092  protected ClusterInfoProvider provider;
093
094  /**
095   * The constructor that uses the basic MetricsBalancer
096   */
097  protected BaseLoadBalancer() {
098    this(null);
099  }
100
101  /**
102   * This Constructor accepts an instance of MetricsBalancer, which will be used instead of creating
103   * a new one
104   */
105  protected BaseLoadBalancer(MetricsBalancer metricsBalancer) {
106    this.metricsBalancer = (metricsBalancer != null) ? metricsBalancer : new MetricsBalancer();
107  }
108
109  protected final Configuration getConf() {
110    return provider.getConfiguration();
111  }
112
113  @Override
114  public void updateClusterMetrics(ClusterMetrics st) {
115    this.clusterStatus = st;
116    if (useRegionFinder) {
117      regionFinder.setClusterMetrics(st);
118    }
119  }
120
121  @Override
122  public void setClusterInfoProvider(ClusterInfoProvider provider) {
123    this.provider = provider;
124  }
125
126  @Override
127  public void postMasterStartupInitialize() {
128    if (provider != null && regionFinder != null) {
129      regionFinder.refreshAndWait(provider.getAssignedRegions());
130    }
131  }
132
133  protected final boolean idleRegionServerExist(BalancerClusterState c) {
134    boolean isServerExistsWithMoreRegions = false;
135    boolean isServerExistsWithZeroRegions = false;
136    for (int[] serverList : c.regionsPerServer) {
137      if (serverList.length > 1) {
138        isServerExistsWithMoreRegions = true;
139      }
140      if (serverList.length == 0) {
141        isServerExistsWithZeroRegions = true;
142      }
143    }
144    return isServerExistsWithMoreRegions && isServerExistsWithZeroRegions;
145  }
146
147  protected final boolean sloppyRegionServerExist(ClusterLoadState cs) {
148    if (slop < 0) {
149      LOG.debug("Slop is less than zero, not checking for sloppiness.");
150      return false;
151    }
152    float average = cs.getLoadAverage(); // for logging
153    int floor = (int) Math.floor(average * (1 - slop));
154    int ceiling = (int) Math.ceil(average * (1 + slop));
155    int maxLoad = cs.getMaxLoad();
156    int minLoad = cs.getMinLoad();
157    if (!(maxLoad > ceiling || minLoad < floor)) {
158      NavigableMap<ServerAndLoad, List<RegionInfo>> serversByLoad = cs.getServersByLoad();
159      if (LOG.isTraceEnabled()) {
160        // If nothing to balance, then don't say anything unless trace-level logging.
161        LOG.trace("Skipping load balancing because balanced cluster; " + "servers="
162          + cs.getNumServers() + " regions=" + cs.getNumRegions() + " average=" + average
163          + " mostloaded=" + serversByLoad.lastKey().getLoad() + " leastloaded="
164          + serversByLoad.firstKey().getLoad());
165      }
166      return false;
167    }
168    return true;
169  }
170
171  /**
172   * Generates a bulk assignment plan to be used on cluster startup using a simple round-robin
173   * assignment.
174   * <p/>
175   * Takes a list of all the regions and all the servers in the cluster and returns a map of each
176   * server to the regions that it should be assigned.
177   * <p/>
178   * Currently implemented as a round-robin assignment. Same invariant as load balancing, all
179   * servers holding floor(avg) or ceiling(avg). TODO: Use block locations from HDFS to place
180   * regions with their blocks
181   * @param regions all regions
182   * @param servers all servers
183   * @return map of server to the regions it should take, or emptyMap if no assignment is possible
184   *         (ie. no servers)
185   */
186  @Override
187  @NonNull
188  public Map<ServerName, List<RegionInfo>> roundRobinAssignment(List<RegionInfo> regions,
189    List<ServerName> servers) throws HBaseIOException {
190    metricsBalancer.incrMiscInvocations();
191    int numServers = servers == null ? 0 : servers.size();
192    if (numServers == 0) {
193      LOG.warn("Wanted to do round robin assignment but no servers to assign to");
194      return Collections.singletonMap(BOGUS_SERVER_NAME, new ArrayList<>(regions));
195    }
196
197    // TODO: instead of retainAssignment() and roundRobinAssignment(), we should just run the
198    // normal LB.balancerCluster() with unassignedRegions. We only need to have a candidate
199    // generator for AssignRegionAction. The LB will ensure the regions are mostly local
200    // and balanced. This should also run fast with fewer number of iterations.
201
202    if (numServers == 1) { // Only one server, nothing fancy we can do here
203      return Collections.singletonMap(servers.get(0), new ArrayList<>(regions));
204    }
205
206    BalancerClusterState cluster = createCluster(servers, regions);
207    Map<ServerName, List<RegionInfo>> assignments = new HashMap<>();
208    roundRobinAssignment(cluster, regions, servers, assignments);
209    return Collections.unmodifiableMap(assignments);
210  }
211
212  private BalancerClusterState createCluster(List<ServerName> servers,
213    Collection<RegionInfo> regions) throws HBaseIOException {
214    boolean hasRegionReplica = false;
215    try {
216      if (provider != null) {
217        hasRegionReplica = provider.hasRegionReplica(regions);
218      }
219    } catch (IOException ioe) {
220      throw new HBaseIOException(ioe);
221    }
222
223    // Get the snapshot of the current assignments for the regions in question, and then create
224    // a cluster out of it. Note that we might have replicas already assigned to some servers
225    // earlier. So we want to get the snapshot to see those assignments, but this will only contain
226    // replicas of the regions that are passed (for performance).
227    Map<ServerName, List<RegionInfo>> clusterState = null;
228    if (!hasRegionReplica) {
229      clusterState = getRegionAssignmentsByServer(regions);
230    } else {
231      // for the case where we have region replica it is better we get the entire cluster's snapshot
232      clusterState = getRegionAssignmentsByServer(null);
233    }
234
235    for (ServerName server : servers) {
236      if (!clusterState.containsKey(server)) {
237        clusterState.put(server, Collections.emptyList());
238      }
239    }
240    return new BalancerClusterState(regions, clusterState, null, this.regionFinder, rackManager,
241      null);
242  }
243
244  private List<ServerName> findIdleServers(List<ServerName> servers) {
245    return provider.getOnlineServersListWithPredicator(servers,
246      metrics -> metrics.getRegionMetrics().isEmpty());
247  }
248
249  /**
250   * Used to assign a single region to a random server.
251   */
252  @Override
253  public ServerName randomAssignment(RegionInfo regionInfo, List<ServerName> servers)
254    throws HBaseIOException {
255    metricsBalancer.incrMiscInvocations();
256    int numServers = servers == null ? 0 : servers.size();
257    if (numServers == 0) {
258      LOG.warn("Wanted to retain assignment but no servers to assign to");
259      return null;
260    }
261    if (numServers == 1) { // Only one server, nothing fancy we can do here
262      return servers.get(0);
263    }
264    List<ServerName> idleServers = findIdleServers(servers);
265    if (idleServers.size() == 1) {
266      return idleServers.get(0);
267    }
268    final List<ServerName> finalServers = idleServers.isEmpty() ? servers : idleServers;
269    List<RegionInfo> regions = Lists.newArrayList(regionInfo);
270    BalancerClusterState cluster = createCluster(finalServers, regions);
271    return randomAssignment(cluster, regionInfo, finalServers);
272  }
273
274  /**
275   * Generates a bulk assignment startup plan, attempting to reuse the existing assignment
276   * information from META, but adjusting for the specified list of available/online servers
277   * available for assignment.
278   * <p>
279   * Takes a map of all regions to their existing assignment from META. Also takes a list of online
280   * servers for regions to be assigned to. Attempts to retain all assignment, so in some instances
281   * initial assignment will not be completely balanced.
282   * <p>
283   * Any leftover regions without an existing server to be assigned to will be assigned randomly to
284   * available servers.
285   * @param regions regions and existing assignment from meta
286   * @param servers available servers
287   * @return map of servers and regions to be assigned to them, or emptyMap if no assignment is
288   *         possible (ie. no servers)
289   */
290  @Override
291  @NonNull
292  public Map<ServerName, List<RegionInfo>> retainAssignment(Map<RegionInfo, ServerName> regions,
293    List<ServerName> servers) throws HBaseIOException {
294    // Update metrics
295    metricsBalancer.incrMiscInvocations();
296    int numServers = servers == null ? 0 : servers.size();
297    if (numServers == 0) {
298      LOG.warn("Wanted to do retain assignment but no servers to assign to");
299      return Collections.singletonMap(BOGUS_SERVER_NAME, new ArrayList<>(regions.keySet()));
300    }
301
302    if (numServers == 1) { // Only one server, nothing fancy we can do here
303      return Collections.singletonMap(servers.get(0), new ArrayList<>(regions.keySet()));
304    }
305
306    // Group all the old assignments by their hostname.
307    // We can't group directly by ServerName since the servers all have
308    // new start-codes.
309
310    // Group the servers by their hostname. It's possible we have multiple
311    // servers on the same host on different ports.
312    Map<ServerName, List<RegionInfo>> assignments = new HashMap<>();
313    ArrayListMultimap<String, ServerName> serversByHostname = ArrayListMultimap.create();
314    for (ServerName server : servers) {
315      assignments.put(server, new ArrayList<>());
316      serversByHostname.put(server.getHostnameLowerCase(), server);
317    }
318
319    // Collection of the hostnames that used to have regions
320    // assigned, but for which we no longer have any RS running
321    // after the cluster restart.
322    Set<String> oldHostsNoLongerPresent = Sets.newTreeSet();
323
324    // If the old servers aren't present, lets assign those regions later.
325    List<RegionInfo> randomAssignRegions = Lists.newArrayList();
326
327    int numRandomAssignments = 0;
328    int numRetainedAssigments = 0;
329    for (Map.Entry<RegionInfo, ServerName> entry : regions.entrySet()) {
330      RegionInfo region = entry.getKey();
331      ServerName oldServerName = entry.getValue();
332      List<ServerName> localServers = new ArrayList<>();
333      if (oldServerName != null) {
334        localServers = serversByHostname.get(oldServerName.getHostnameLowerCase());
335      }
336      if (localServers.isEmpty()) {
337        // No servers on the new cluster match up with this hostname, assign randomly, later.
338        randomAssignRegions.add(region);
339        if (oldServerName != null) {
340          oldHostsNoLongerPresent.add(oldServerName.getHostnameLowerCase());
341        }
342      } else if (localServers.size() == 1) {
343        // the usual case - one new server on same host
344        ServerName target = localServers.get(0);
345        assignments.get(target).add(region);
346        numRetainedAssigments++;
347      } else {
348        // multiple new servers in the cluster on this same host
349        if (localServers.contains(oldServerName)) {
350          assignments.get(oldServerName).add(region);
351          numRetainedAssigments++;
352        } else {
353          ServerName target = null;
354          for (ServerName tmp : localServers) {
355            if (tmp.getPort() == oldServerName.getPort()) {
356              target = tmp;
357              assignments.get(tmp).add(region);
358              numRetainedAssigments++;
359              break;
360            }
361          }
362          if (target == null) {
363            randomAssignRegions.add(region);
364          }
365        }
366      }
367    }
368
369    // If servers from prior assignment aren't present, then lets do randomAssignment on regions.
370    if (randomAssignRegions.size() > 0) {
371      BalancerClusterState cluster = createCluster(servers, regions.keySet());
372      for (Map.Entry<ServerName, List<RegionInfo>> entry : assignments.entrySet()) {
373        ServerName sn = entry.getKey();
374        for (RegionInfo region : entry.getValue()) {
375          cluster.doAssignRegion(region, sn);
376        }
377      }
378      for (RegionInfo region : randomAssignRegions) {
379        ServerName target = randomAssignment(cluster, region, servers);
380        assignments.get(target).add(region);
381        numRandomAssignments++;
382      }
383    }
384
385    String randomAssignMsg = "";
386    if (numRandomAssignments > 0) {
387      randomAssignMsg = numRandomAssignments + " regions were assigned "
388        + "to random hosts, since the old hosts for these regions are no "
389        + "longer present in the cluster. These hosts were:\n  "
390        + Joiner.on("\n  ").join(oldHostsNoLongerPresent);
391    }
392
393    LOG.info("Reassigned " + regions.size() + " regions. " + numRetainedAssigments
394      + " retained the pre-restart assignment. " + randomAssignMsg);
395    return Collections.unmodifiableMap(assignments);
396  }
397
398  protected float getDefaultSlop() {
399    return REGIONS_SLOP_DEFAULT;
400  }
401
402  private RegionHDFSBlockLocationFinder createRegionLocationFinder(Configuration conf) {
403    RegionHDFSBlockLocationFinder finder = new RegionHDFSBlockLocationFinder();
404    finder.setConf(conf);
405    finder.setClusterInfoProvider(provider);
406    return finder;
407  }
408
409  protected void loadConf(Configuration conf) {
410    this.slop = conf.getFloat(REGIONS_SLOP_KEY, getDefaultSlop());
411    this.rackManager = new RackManager(conf);
412    useRegionFinder = conf.getBoolean("hbase.master.balancer.uselocality", true);
413    if (useRegionFinder) {
414      regionFinder = createRegionLocationFinder(conf);
415    } else {
416      regionFinder = null;
417    }
418    this.isByTable = conf.getBoolean(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE,
419      DEFAULT_HBASE_MASTER_LOADBALANCE_BYTABLE);
420    // Print out base configs. Don't print overallSlop since it for simple balancer exclusively.
421    LOG.info("slop={}", this.slop);
422  }
423
424  @Override
425  public void initialize() {
426    loadConf(getConf());
427  }
428
429  @Override
430  public void regionOnline(RegionInfo regionInfo, ServerName sn) {
431  }
432
433  @Override
434  public void regionOffline(RegionInfo regionInfo) {
435  }
436
437  @Override
438  public boolean isStopped() {
439    return stopped;
440  }
441
442  @Override
443  public void stop(String why) {
444    LOG.info("Load Balancer stop requested: {}", why);
445    stopped = true;
446  }
447
448  /**
449   * Updates the balancer status tag reported to JMX
450   */
451  @Override
452  public void updateBalancerStatus(boolean status) {
453    metricsBalancer.balancerStatus(status);
454  }
455
456  /**
457   * Used to assign a single region to a random server.
458   */
459  private ServerName randomAssignment(BalancerClusterState cluster, RegionInfo regionInfo,
460    List<ServerName> servers) {
461    int numServers = servers.size(); // servers is not null, numServers > 1
462    ServerName sn = null;
463    final int maxIterations = numServers * 4;
464    int iterations = 0;
465    List<ServerName> usedSNs = new ArrayList<>(servers.size());
466    Random rand = ThreadLocalRandom.current();
467    do {
468      int i = rand.nextInt(numServers);
469      sn = servers.get(i);
470      if (!usedSNs.contains(sn)) {
471        usedSNs.add(sn);
472      }
473    } while (cluster.wouldLowerAvailability(regionInfo, sn) && iterations++ < maxIterations);
474    if (iterations >= maxIterations) {
475      // We have reached the max. Means the servers that we collected is still lowering the
476      // availability
477      for (ServerName unusedServer : servers) {
478        if (!usedSNs.contains(unusedServer)) {
479          // check if any other unused server is there for us to use.
480          // If so use it. Else we have not other go but to go with one of them
481          if (!cluster.wouldLowerAvailability(regionInfo, unusedServer)) {
482            sn = unusedServer;
483            break;
484          }
485        }
486      }
487    }
488    cluster.doAssignRegion(regionInfo, sn);
489    return sn;
490  }
491
492  /**
493   * Round-robin a list of regions to a list of servers
494   */
495  private void roundRobinAssignment(BalancerClusterState cluster, List<RegionInfo> regions,
496    List<ServerName> servers, Map<ServerName, List<RegionInfo>> assignments) {
497    Random rand = ThreadLocalRandom.current();
498    List<RegionInfo> unassignedRegions = new ArrayList<>();
499    int numServers = servers.size();
500    int numRegions = regions.size();
501    int max = (int) Math.ceil((float) numRegions / numServers);
502    int serverIdx = 0;
503    if (numServers > 1) {
504      serverIdx = rand.nextInt(numServers);
505    }
506    int regionIdx = 0;
507    for (int j = 0; j < numServers; j++) {
508      ServerName server = servers.get((j + serverIdx) % numServers);
509      List<RegionInfo> serverRegions = new ArrayList<>(max);
510      for (int i = regionIdx; i < numRegions; i += numServers) {
511        RegionInfo region = regions.get(i % numRegions);
512        if (cluster.wouldLowerAvailability(region, server)) {
513          unassignedRegions.add(region);
514        } else {
515          serverRegions.add(region);
516          cluster.doAssignRegion(region, server);
517        }
518      }
519      assignments.put(server, serverRegions);
520      regionIdx++;
521    }
522
523    List<RegionInfo> lastFewRegions = new ArrayList<>();
524    // assign the remaining by going through the list and try to assign to servers one-by-one
525    serverIdx = rand.nextInt(numServers);
526    for (RegionInfo region : unassignedRegions) {
527      boolean assigned = false;
528      for (int j = 0; j < numServers; j++) { // try all servers one by one
529        ServerName server = servers.get((j + serverIdx) % numServers);
530        if (cluster.wouldLowerAvailability(region, server)) {
531          continue;
532        } else {
533          assignments.computeIfAbsent(server, k -> new ArrayList<>()).add(region);
534          cluster.doAssignRegion(region, server);
535          serverIdx = (j + serverIdx + 1) % numServers; // remain from next server
536          assigned = true;
537          break;
538        }
539      }
540      if (!assigned) {
541        lastFewRegions.add(region);
542      }
543    }
544    // just sprinkle the rest of the regions on random regionservers. The balanceCluster will
545    // make it optimal later. we can end up with this if numReplicas > numServers.
546    for (RegionInfo region : lastFewRegions) {
547      int i = rand.nextInt(numServers);
548      ServerName server = servers.get(i);
549      assignments.computeIfAbsent(server, k -> new ArrayList<>()).add(region);
550      cluster.doAssignRegion(region, server);
551    }
552  }
553
554  // return a modifiable map, as we may add more entries into the returned map.
555  private Map<ServerName, List<RegionInfo>>
556    getRegionAssignmentsByServer(Collection<RegionInfo> regions) {
557    return provider != null
558      ? new HashMap<>(provider.getSnapShotOfAssignment(regions))
559      : new HashMap<>();
560  }
561
562  protected final Map<ServerName, List<RegionInfo>>
563    toEnsumbleTableLoad(Map<TableName, Map<ServerName, List<RegionInfo>>> LoadOfAllTable) {
564    Map<ServerName, List<RegionInfo>> returnMap = new TreeMap<>();
565    for (Map<ServerName, List<RegionInfo>> serverNameListMap : LoadOfAllTable.values()) {
566      serverNameListMap.forEach((serverName, regionInfoList) -> {
567        List<RegionInfo> regionInfos =
568          returnMap.computeIfAbsent(serverName, k -> new ArrayList<>());
569        regionInfos.addAll(regionInfoList);
570      });
571    }
572    return returnMap;
573  }
574
575  /**
576   * Perform the major balance operation for table, all sub classes should override this method.
577   * <p/>
578   * Will be invoked by {@link #balanceCluster(Map)}. If
579   * {@link HConstants#HBASE_MASTER_LOADBALANCE_BYTABLE} is enabled, we will call this method
580   * multiple times, one table a time, where we will only pass in the regions for a single table
581   * each time. If not, we will pass in all the regions at once, and the {@code tableName} will be
582   * {@link HConstants#ENSEMBLE_TABLE_NAME}.
583   * @param tableName      the table to be balanced
584   * @param loadOfOneTable region load of servers for the specific one table
585   * @return List of plans
586   */
587  protected abstract List<RegionPlan> balanceTable(TableName tableName,
588    Map<ServerName, List<RegionInfo>> loadOfOneTable);
589
590  /**
591   * Called before actually executing balanceCluster. The sub classes could override this method to
592   * do some initialization work.
593   */
594  protected void
595    preBalanceCluster(Map<TableName, Map<ServerName, List<RegionInfo>>> loadOfAllTable) {
596  }
597
598  /**
599   * Perform the major balance operation for cluster, will invoke
600   * {@link #balanceTable(TableName, Map)} to do actual balance.
601   * <p/>
602   * THIs method is marked as final which means you should not override this method. See the javadoc
603   * for {@link #balanceTable(TableName, Map)} for more details.
604   * @param loadOfAllTable region load of servers for all table
605   * @return a list of regions to be moved, including source and destination, or null if cluster is
606   *         already balanced
607   * @see #balanceTable(TableName, Map)
608   */
609  @Override
610  public final List<RegionPlan>
611    balanceCluster(Map<TableName, Map<ServerName, List<RegionInfo>>> loadOfAllTable) {
612    preBalanceCluster(loadOfAllTable);
613    if (isByTable) {
614      List<RegionPlan> result = new ArrayList<>();
615      loadOfAllTable.forEach((tableName, loadOfOneTable) -> {
616        LOG.info("Start Generate Balance plan for table: " + tableName);
617        List<RegionPlan> partialPlans = balanceTable(tableName, loadOfOneTable);
618        if (partialPlans != null) {
619          result.addAll(partialPlans);
620        }
621      });
622      return result;
623    } else {
624      LOG.debug("Start Generate Balance plan for cluster.");
625      return balanceTable(HConstants.ENSEMBLE_TABLE_NAME, toEnsumbleTableLoad(loadOfAllTable));
626    }
627  }
628
629  @Override
630  public void onConfigurationChange(Configuration conf) {
631    loadConf(conf);
632  }
633}