diff --git a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BalancerClusterState.java b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BalancerClusterState.java index 4b3809c107cb..ca08a6af3015 100644 --- a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BalancerClusterState.java +++ b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BalancerClusterState.java @@ -109,6 +109,7 @@ class BalancerClusterState { int numMovedRegions = 0; // num moved regions from the initial configuration Map> clusterState; + Map> allTablesClusterState; private final RackManager rackManager; // Maps region -> rackIndex -> locality of region on rack diff --git a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java index 54516868a0a0..8172bc039f1f 100644 --- a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java +++ b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/BaseLoadBalancer.java @@ -590,6 +590,13 @@ protected abstract List balanceTable(TableName tableName, preBalanceCluster(Map>> loadOfAllTable) { } + /** + * Called after executing balanceCluster. The sub classes could override this method to + * do some cleanup work. + */ + protected void completeBalancerCosts(Map>> loadOfAllTable) { + } + /** * Perform the major balance operation for cluster, will invoke * {@link #balanceTable(TableName, Map)} to do actual balance. @@ -605,20 +612,24 @@ protected abstract List balanceTable(TableName tableName, public final List balanceCluster(Map>> loadOfAllTable) { preBalanceCluster(loadOfAllTable); + List result; if (isByTable) { - List result = new ArrayList<>(); - loadOfAllTable.forEach((tableName, loadOfOneTable) -> { + result = new ArrayList<>(); + loadOfAllTable.forEach((tableName, loadOfOneTable) -> { LOG.info("Start Generate Balance plan for table: " + tableName); List partialPlans = balanceTable(tableName, loadOfOneTable); if (partialPlans != null) { result.addAll(partialPlans); } }); - return result; } else { LOG.debug("Start Generate Balance plan for cluster."); - return balanceTable(HConstants.ENSEMBLE_TABLE_NAME, toEnsumbleTableLoad(loadOfAllTable)); + result = balanceTable(HConstants.ENSEMBLE_TABLE_NAME, toEnsumbleTableLoad(loadOfAllTable)); } + //run at cluster + //addall to partialplans + completeBalancerCosts(loadOfAllTable); + return result; } @Override diff --git a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFunction.java b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFunction.java index 5cc98478f9b9..af8cd4848590 100644 --- a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFunction.java +++ b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/CostFunction.java @@ -113,4 +113,12 @@ protected static double scale(double min, double max, double value) { return Math.max(0d, Math.min(1d, (value - min) / (max - min))); } + + /** + * Called once per LB invocation to give the cost function to carry out any + * state clean up. + */ + void complete(BalancerClusterState cluster) { + this.cluster = cluster; + } } diff --git a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/MutuallyExclusiveTablesCostFunction.java b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/MutuallyExclusiveTablesCostFunction.java new file mode 100644 index 000000000000..1972c2731d74 --- /dev/null +++ b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/MutuallyExclusiveTablesCostFunction.java @@ -0,0 +1,113 @@ +/* + * 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 org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.yetus.audience.InterfaceAudience; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Ensure Tables are mutually exclusive on an RS + * for example: META and SYSTEM.CATALOG can be mutually exclusive Tables + * on same RS + */ +@InterfaceAudience.Private +public class MutuallyExclusiveTablesCostFunction extends CostFunction { + public static final String MUTUALLY_EXCLUSIVE_TABLES_KEY = + "hbase.master.balancer.stochastic.mutuallyExclusiveTables"; + public static final String MUTUALLY_EXCLUSIVE_TABLES_COST_KEY = + "hbase.master.balancer.stochastic.mutuallyExclusiveTablesCost"; + public static final float DEFAULT_MUTUALLY_EXCLUSIVE_TABLES_COST = 10000; + private Set mutuallyExclusiveTables = Collections.emptySet(); + private static final Map> sharedState = new HashMap<>(); + + + public MutuallyExclusiveTablesCostFunction(Configuration conf) { + this.setMultiplier( + conf.getFloat(MUTUALLY_EXCLUSIVE_TABLES_COST_KEY, DEFAULT_MUTUALLY_EXCLUSIVE_TABLES_COST)); + initializeMutuallyExclusiveTables(conf); + } + + private void initializeMutuallyExclusiveTables(Configuration conf) { + String[] tables = conf.getStrings(MUTUALLY_EXCLUSIVE_TABLES_KEY); + if (tables != null && tables.length > 0) { + this.mutuallyExclusiveTables = new HashSet<>(Arrays.asList(tables)); + } + } + + @Override void prepare(BalancerClusterState cluster) { + super.prepare(cluster); + // Register the current table's regions in the shared state + Map> clusterState = this.cluster.clusterState; + + for (Map.Entry> entry : clusterState.entrySet()) { + Set tablesOnServer = sharedState.computeIfAbsent(entry.getKey(), k -> new HashSet<>()); + + for (RegionInfo regionInfo : entry.getValue()) { + String tableName = regionInfo.getTable().getNameAsString(); + if (mutuallyExclusiveTables.contains(tableName)) { + tablesOnServer.add(tableName); + } + } + } + } + + @Override void complete(BalancerClusterState cluster) { + super.complete(cluster); + // Clear the shared state + sharedState.clear(); + } + + @Override protected double cost() { + double totalCost = 0; + + Map> clusterState = this.cluster.clusterState; + + for (Map.Entry> entry : clusterState.entrySet()) { + List regions = entry.getValue(); + Set exclusiveTablesOnServer = new HashSet<>(); + if(totalCost == 1) + break; + for (RegionInfo regionInfo : regions) { + String tableName = regionInfo.getTable().getNameAsString(); + if (mutuallyExclusiveTables.contains(tableName)) { + // If its already seen table, continue + if (exclusiveTablesOnServer.contains(tableName)) { + continue; + } + exclusiveTablesOnServer.add(tableName); + if (exclusiveTablesOnServer.size() > 1) { + totalCost = 1; + break; + } + } + } + } + + return totalCost; + } +} diff --git a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java index e5cd5446c5c8..a2d2868b0bc0 100644 --- a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java +++ b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java @@ -180,6 +180,25 @@ public StochasticLoadBalancer(MetricsStochasticBalancer metricsStochasticBalance super(metricsStochasticBalancer); } + @Override protected void completeBalancerCosts( + Map>> loadOfAllTable) { + if (isByTable) { + loadOfAllTable.forEach((tableName, loadOfOneTable) -> { + LOG.info("Complete Balancer plan for table: " + tableName); + BalancerClusterState cluster = + new BalancerClusterState(loadOfOneTable, loads, null, rackManager, + regionCacheRatioOnOldServerMap); + completeCosts(cluster); + }); + } else { + LOG.debug("Complete Balancer plan for cluster."); + BalancerClusterState cluster = + new BalancerClusterState(toEnsumbleTableLoad(loadOfAllTable), loads, null, rackManager, + regionCacheRatioOnOldServerMap); + completeCosts(cluster); + } + } + private static CostFunction createCostFunction(Class clazz, Configuration conf) { try { @@ -754,6 +773,14 @@ void initCosts(BalancerClusterState cluster) { } } + @RestrictedApi(explanation = "Should only be called in tests", link = "", + allowedOnPath = ".*(/src/test/.*|StochasticLoadBalancer).java") + void completeCosts(BalancerClusterState cluster) { + for (CostFunction c : costFunctions) { + c.complete(cluster); + } + } + /** * Update both the costs of costfunctions and the weights of candidate generators */ diff --git a/hbase-balancer/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java b/hbase-balancer/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java index cc16cfe2ec83..612fdbb3f70f 100644 --- a/hbase-balancer/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java +++ b/hbase-balancer/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java @@ -17,6 +17,8 @@ */ package org.apache.hadoop.hbase.master.balancer; +import static org.apache.hadoop.hbase.master.balancer.MutuallyExclusiveTablesCostFunction.DEFAULT_MUTUALLY_EXCLUSIVE_TABLES_COST; +import static org.apache.hadoop.hbase.master.balancer.MutuallyExclusiveTablesCostFunction.MUTUALLY_EXCLUSIVE_TABLES_KEY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -45,6 +47,7 @@ import org.apache.hadoop.hbase.Size; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.hadoop.hbase.client.RegionInfoBuilder; import org.apache.hadoop.hbase.master.RegionPlan; import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MediumTests; @@ -401,6 +404,43 @@ public void testLocalityCost() throws Exception { } } + @Test + public void testMutuallyExclusiveTablesOnDifferentServers() throws Exception + { + conf.setStrings(MUTUALLY_EXCLUSIVE_TABLES_KEY, "table1", "table2"); + loadBalancer.onConfigurationChange(conf); + Map> clusterState = new HashMap<>(); + ServerName host1 = ServerName.valueOf("host1", 1000, 1000); + ServerName host2 = ServerName.valueOf("host2", 1000, 1000); + RegionInfo region1 = RegionInfoBuilder.newBuilder(TableName.valueOf("table1")).build(); + RegionInfo region2 = RegionInfoBuilder.newBuilder(TableName.valueOf("table2")).build(); + clusterState.put(host1 , Arrays.asList(region1)); + clusterState.put(host2 , Arrays.asList(region2)); + CostFunction costFunction = new MutuallyExclusiveTablesCostFunction(conf); + costFunction.prepare(new BalancerClusterState(clusterState, null, null, null)); + double cost = costFunction.cost(); + assertEquals(0.0, cost, 0.001); + } + + @Test + public void testMutuallyExclusiveTablesOnSameServer() throws Exception + { + conf.setStrings(MUTUALLY_EXCLUSIVE_TABLES_KEY, "table1", "table2"); + loadBalancer.onConfigurationChange(conf); + Map> clusterState = new HashMap<>(); + ServerName host1 = ServerName.valueOf("host1", 1000, 1000); + RegionInfo region1 = RegionInfoBuilder.newBuilder(TableName.valueOf("table1")).build(); + RegionInfo region2 = RegionInfoBuilder.newBuilder(TableName.valueOf("table2")).build(); + List regions = new ArrayList<>(); + regions.add(region1); + regions.add(region2); + clusterState.put(host1 , regions); + CostFunction costFunction = new MutuallyExclusiveTablesCostFunction(conf); + costFunction.prepare(new BalancerClusterState(clusterState, null, null, null)); + double cost = costFunction.cost(); + assertEquals(1, cost, 0.001); + } + @Test public void testMoveCostMultiplier() throws Exception { Configuration conf = HBaseConfiguration.create();