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}