private void balanceOverall(List<RegionPlan> regionsToReturn,
    Map<ServerName, BalanceInfo> serverBalanceInfo, boolean fetchFromTail,
    MinMaxPriorityQueue<RegionPlan> regionsToMove, int max, int min) {
    // Step 1.
    // A map to record the plan we have already got as status quo, in order to resolve a cyclic
    // assignment pair,
    // e.g. plan 1: A -> B, plan 2: B ->C => resolve plan1 to A -> C, remove plan2
    Map<ServerName, List<Integer>> returnMap = new HashMap<>();
    for (int i = 0; i < regionsToReturn.size(); i++) {
      List<Integer> pos = returnMap.get(regionsToReturn.get(i).getDestination());
      if (pos == null) {
        pos = new ArrayList<>();
        returnMap.put(regionsToReturn.get(i).getDestination(), pos);
      }
      pos.add(i);
    }

    // Step 2.
    // Peel off one region from each RS which has max number of regions now.
    // Each RS should have either max or min numbers of regions for this table.
    for (int i = 0; i < serverLoadList.size(); i++) {
      ServerAndLoad serverload = serverLoadList.get(i);
      BalanceInfo balanceInfo = serverBalanceInfo.get(serverload.getServerName());
      if (balanceInfo == null) {
        continue;
      }
      setLoad(serverLoadList, i, balanceInfo.getNumRegionsAdded());
      if (balanceInfo.getHriList().size() + balanceInfo.getNumRegionsAdded() == max) {
        RegionInfo hriToPlan;
        if (balanceInfo.getHriList().isEmpty()) {
          LOG.debug("During balanceOverall, we found " + serverload.getServerName()
            + " has no RegionInfo, no operation needed");
          continue;
        } else if (balanceInfo.getNextRegionForUnload() >= balanceInfo.getHriList().size()) {
          continue;
        } else {
          hriToPlan = balanceInfo.getHriList().get(balanceInfo.getNextRegionForUnload());
        }
        RegionPlan maxPlan = new RegionPlan(hriToPlan, serverload.getServerName(), null);
        regionsToMove.add(maxPlan);
        setLoad(serverLoadList, i, -1);
      } else if (
        balanceInfo.getHriList().size() + balanceInfo.getNumRegionsAdded() > max
          || balanceInfo.getHriList().size() + balanceInfo.getNumRegionsAdded() < min
      ) {
        LOG.warn(
          "Encounter incorrect region numbers after calculating move plan during balanceOverall, "
            + "for this table, " + serverload.getServerName() + " originally has "
            + balanceInfo.getHriList().size() + " regions and " + balanceInfo.getNumRegionsAdded()
            + " regions have been added. Yet, max =" + max + ", min =" + min
            + ". Thus stop balance for this table"); // should not happen
        return;
      }
    }

    // Step 3. sort the ServerLoadList, the ArrayList hold overall load for each server.
    // We only need to assign the regionsToMove to
    // the first n = regionsToMove.size() RS that has least load.
    Collections.sort(serverLoadList, new Comparator<ServerAndLoad>() {
      @Override
      public int compare(ServerAndLoad s1, ServerAndLoad s2) {
        if (s1.getLoad() == s2.getLoad()) {
          return 0;
        } else {
          return (s1.getLoad() > s2.getLoad()) ? 1 : -1;
        }
      }
    });

    // Step 4.
    // Preparation before assign out all regionsToMove.
    // We need to remove the plan that has the source RS equals to destination RS,
    // since the source RS belongs to the least n loaded RS.
    int assignLength = regionsToMove.size();
    // A structure help to map ServerName to it's load and index in ServerLoadList
    Map<ServerName, Pair<ServerAndLoad, Integer>> SnLoadMap = new HashMap<>();
    for (int i = 0; i < serverLoadList.size(); i++) {
      SnLoadMap.put(serverLoadList.get(i).getServerName(), new Pair<>(serverLoadList.get(i), i));
    }
    Pair<ServerAndLoad, Integer> shredLoad;
    // A List to help mark the plan in regionsToMove that should be removed
    List<RegionPlan> planToRemoveList = new ArrayList<>();
    // A structure to record how many times a server becomes the source of a plan, from
    // regionsToMove.
    Map<ServerName, Integer> sourceMap = new HashMap<>();
    // We remove one of the plan which would cause source RS equals destination RS.
    // But we should keep in mind that the second plan from such RS should be kept.
    for (RegionPlan plan : regionsToMove) {
      // the source RS's load and index in ServerLoadList
      shredLoad = SnLoadMap.get(plan.getSource());
      if (!sourceMap.containsKey(plan.getSource())) {
        sourceMap.put(plan.getSource(), 0);
      }
      sourceMap.put(plan.getSource(), sourceMap.get(plan.getSource()) + 1);
      if (shredLoad.getSecond() < assignLength && sourceMap.get(plan.getSource()) == 1) {
        planToRemoveList.add(plan);
        // While marked as to be removed, the count should be add back to the source RS
        setLoad(serverLoadList, shredLoad.getSecond(), 1);
      }
    }
    // Remove those marked plans from regionsToMove,
    // we cannot direct remove them during iterating through
    // regionsToMove, due to the fact that regionsToMove is a MinMaxPriorityQueue.
    for (RegionPlan planToRemove : planToRemoveList) {
      regionsToMove.remove(planToRemove);
    }

    // Step 5.
    // We only need to assign the regionsToMove to
    // the first n = regionsToMove.size() of them, with least load.
    // With this strategy adopted, we can gradually achieve the overall balance,
    // while keeping table level balanced.
    for (int i = 0; i < assignLength; i++) {
      // skip the RS that is also the source, we have removed them from regionsToMove in previous
      // step
      if (sourceMap.containsKey(serverLoadList.get(i).getServerName())) {
        continue;
      }
      addRegionPlan(regionsToMove, fetchFromTail, serverLoadList.get(i).getServerName(),
        regionsToReturn);
      setLoad(serverLoadList, i, 1);
      // resolve a possible cyclic assignment pair if we just produced one:
      // e.g. plan1: A -> B, plan2: B -> C => resolve plan1 to A -> C and remove plan2
      List<Integer> pos =
        returnMap.get(regionsToReturn.get(regionsToReturn.size() - 1).getSource());
      if (pos != null && pos.size() != 0) {
        regionsToReturn.get(pos.get(pos.size() - 1))
          .setDestination(regionsToReturn.get(regionsToReturn.size() - 1).getDestination());
        pos.remove(pos.size() - 1);
        regionsToReturn.remove(regionsToReturn.size() - 1);
      }
    }
    // Done balance overall
  }
If we need to balanceoverall, we need to add one more round to peel off one region from each
max. Together with other regions left to be assigned, we distribute all regionToMove, to the RS
that have less regions in whole cluster scope.
hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/SimpleLoadBalancer.java