Skip to content

Commit 836f269

Browse files
PierreZwchevreuil
authored andcommitted
HBASE-22618 added the possibility to load custom cost functions
Signed-off-by: Wellington Chevreuil <[email protected]>
1 parent 8cb531f commit 836f269

File tree

3 files changed

+125
-37
lines changed

3 files changed

+125
-37
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import java.util.LinkedList;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Objects;
3031
import java.util.Random;
32+
import java.util.stream.Collectors;
3133

3234
import org.apache.hadoop.conf.Configuration;
3335
import org.apache.hadoop.hbase.ClusterMetrics;
@@ -47,6 +49,7 @@
4749
import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.MoveRegionAction;
4850
import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster.SwapRegionsAction;
4951
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
52+
import org.apache.hadoop.hbase.util.ReflectionUtils;
5053
import org.apache.yetus.audience.InterfaceAudience;
5154
import org.slf4j.Logger;
5255
import org.slf4j.LoggerFactory;
@@ -82,6 +85,13 @@
8285
* <li>hbase.master.balancer.stochastic.storefileSizeCost</li>
8386
* </ul>
8487
*
88+
* <p>You can also add custom Cost function by setting the the following configuration value:</p>
89+
* <ul>
90+
* <li>hbase.master.balancer.stochastic.additionalCostFunctions</li>
91+
* </ul>
92+
*
93+
* <p>All custom Cost Functions needs to extends {@link StochasticLoadBalancer.CostFunction}</p>
94+
*
8595
* <p>In addition to the above configurations, the balancer can be tuned by the following
8696
* configuration values:</p>
8797
* <ul>
@@ -117,6 +127,8 @@ public class StochasticLoadBalancer extends BaseLoadBalancer {
117127
private static final String TABLE_FUNCTION_SEP = "_";
118128
protected static final String MIN_COST_NEED_BALANCE_KEY =
119129
"hbase.master.balancer.stochastic.minCostNeedBalance";
130+
protected static final String COST_FUNCTIONS_COST_FUNCTIONS_KEY =
131+
"hbase.master.balancer.stochastic.additionalCostFunctions";
120132

121133
protected static final Random RANDOM = new Random(System.currentTimeMillis());
122134
private static final Logger LOG = LoggerFactory.getLogger(StochasticLoadBalancer.class);
@@ -133,7 +145,7 @@ public class StochasticLoadBalancer extends BaseLoadBalancer {
133145

134146
private List<CandidateGenerator> candidateGenerators;
135147
private CostFromRegionLoadFunction[] regionLoadFunctions;
136-
private CostFunction[] costFunctions; // FindBugs: Wants this protected; IS2_INCONSISTENT_SYNC
148+
private List<CostFunction> costFunctions; // FindBugs: Wants this protected; IS2_INCONSISTENT_SYNC
137149

138150
// to save and report costs to JMX
139151
private Double curOverallCost = 0d;
@@ -196,25 +208,57 @@ public synchronized void setConf(Configuration conf) {
196208
};
197209
regionReplicaHostCostFunction = new RegionReplicaHostCostFunction(conf);
198210
regionReplicaRackCostFunction = new RegionReplicaRackCostFunction(conf);
199-
costFunctions = new CostFunction[]{
200-
new RegionCountSkewCostFunction(conf),
201-
new PrimaryRegionCountSkewCostFunction(conf),
202-
new MoveCostFunction(conf),
203-
localityCost,
204-
rackLocalityCost,
205-
new TableSkewCostFunction(conf),
206-
regionReplicaHostCostFunction,
207-
regionReplicaRackCostFunction,
208-
regionLoadFunctions[0],
209-
regionLoadFunctions[1],
210-
regionLoadFunctions[2],
211-
regionLoadFunctions[3],
212-
regionLoadFunctions[4]
213-
};
214-
curFunctionCosts= new Double[costFunctions.length];
215-
tempFunctionCosts= new Double[costFunctions.length];
211+
212+
costFunctions = new ArrayList<>();
213+
costFunctions.add(new RegionCountSkewCostFunction(conf));
214+
costFunctions.add(new PrimaryRegionCountSkewCostFunction(conf));
215+
costFunctions.add(new MoveCostFunction(conf));
216+
costFunctions.add(localityCost);
217+
costFunctions.add(rackLocalityCost);
218+
costFunctions.add(new TableSkewCostFunction(conf));
219+
costFunctions.add(regionReplicaHostCostFunction);
220+
costFunctions.add(regionReplicaRackCostFunction);
221+
costFunctions.add(regionLoadFunctions[0]);
222+
costFunctions.add(regionLoadFunctions[1]);
223+
costFunctions.add(regionLoadFunctions[2]);
224+
costFunctions.add(regionLoadFunctions[3]);
225+
costFunctions.add(regionLoadFunctions[4]);
226+
loadCustomCostFunctions(conf);
227+
228+
curFunctionCosts= new Double[costFunctions.size()];
229+
tempFunctionCosts= new Double[costFunctions.size()];
216230
LOG.info("Loaded config; maxSteps=" + maxSteps + ", stepsPerRegion=" + stepsPerRegion +
217-
", maxRunningTime=" + maxRunningTime + ", isByTable=" + isByTable + ", etc.");
231+
", maxRunningTime=" + maxRunningTime + ", isByTable=" + isByTable + ", CostFunctions=" +
232+
Arrays.toString(getCostFunctionNames()) + " etc.");
233+
}
234+
235+
private void loadCustomCostFunctions(Configuration conf) {
236+
String[] functionsNames = conf.getStrings(COST_FUNCTIONS_COST_FUNCTIONS_KEY);
237+
238+
if (null == functionsNames) {
239+
return;
240+
}
241+
242+
costFunctions.addAll(Arrays.stream(functionsNames)
243+
.map(c -> {
244+
Class<? extends CostFunction> klass = null;
245+
try {
246+
klass = (Class<? extends CostFunction>) Class.forName(c);
247+
} catch (ClassNotFoundException e) {
248+
LOG.warn("Cannot load class " + c + "': " + e.getMessage());
249+
}
250+
if (null == klass) {
251+
return null;
252+
}
253+
254+
CostFunction reflected = ReflectionUtils.newInstance(klass, conf);
255+
LOG.info("Successfully loaded custom CostFunction '" +
256+
reflected.getClass().getSimpleName() + "'");
257+
258+
return reflected;
259+
})
260+
.filter(Objects::nonNull)
261+
.collect(Collectors.toList()));
218262
}
219263

220264
protected void setCandidateGenerators(List<CandidateGenerator> customCandidateGenerators) {
@@ -468,8 +512,8 @@ private void updateStochasticCosts(TableName tableName, Double overall, Double[]
468512
"Overall", "Overall cost", overall);
469513

470514
// each cost function
471-
for (int i = 0; i < costFunctions.length; i++) {
472-
CostFunction costFunction = costFunctions[i];
515+
for (int i = 0; i < costFunctions.size(); i++) {
516+
CostFunction costFunction = costFunctions.get(i);
473517
String costFunctionName = costFunction.getClass().getSimpleName();
474518
Double costPercent = (overall == 0) ? 0 : (subCosts[i] / overall);
475519
// TODO: cost function may need a specific description
@@ -567,9 +611,9 @@ protected void updateCostsWithAction(Cluster cluster, Action action) {
567611
*/
568612
public String[] getCostFunctionNames() {
569613
if (costFunctions == null) return null;
570-
String[] ret = new String[costFunctions.length];
571-
for (int i = 0; i < costFunctions.length; i++) {
572-
CostFunction c = costFunctions[i];
614+
String[] ret = new String[costFunctions.size()];
615+
for (int i = 0; i < costFunctions.size(); i++) {
616+
CostFunction c = costFunctions.get(i);
573617
ret[i] = c.getClass().getSimpleName();
574618
}
575619

@@ -588,8 +632,8 @@ public String[] getCostFunctionNames() {
588632
protected double computeCost(Cluster cluster, double previousCost) {
589633
double total = 0;
590634

591-
for (int i = 0; i < costFunctions.length; i++) {
592-
CostFunction c = costFunctions[i];
635+
for (int i = 0; i < costFunctions.size(); i++) {
636+
CostFunction c = costFunctions.get(i);
593637
this.tempFunctionCosts[i] = 0.0;
594638

595639
if (c.getMultiplier() <= 0) {
@@ -972,13 +1016,13 @@ Cluster.Action generate(Cluster cluster) {
9721016
/**
9731017
* Base class of StochasticLoadBalancer's Cost Functions.
9741018
*/
975-
abstract static class CostFunction {
1019+
public abstract static class CostFunction {
9761020

9771021
private float multiplier = 0;
9781022

9791023
protected Cluster cluster;
9801024

981-
CostFunction(Configuration c) {
1025+
public CostFunction(Configuration c) {
9821026
}
9831027

9841028
boolean isNeeded() {
@@ -1027,7 +1071,7 @@ void postAction(Action action) {
10271071
protected void regionMoved(int region, int oldServer, int newServer) {
10281072
}
10291073

1030-
abstract double cost();
1074+
protected abstract double cost();
10311075

10321076
@SuppressWarnings("checkstyle:linelength")
10331077
/**
@@ -1124,7 +1168,7 @@ static class MoveCostFunction extends CostFunction {
11241168
}
11251169

11261170
@Override
1127-
double cost() {
1171+
protected double cost() {
11281172
// Try and size the max number of Moves, but always be prepared to move some.
11291173
int maxMoves = Math.max((int) (cluster.numRegions * maxMovesPercent),
11301174
DEFAULT_MAX_MOVES);
@@ -1159,7 +1203,7 @@ static class RegionCountSkewCostFunction extends CostFunction {
11591203
}
11601204

11611205
@Override
1162-
double cost() {
1206+
protected double cost() {
11631207
if (stats == null || stats.length != cluster.numServers) {
11641208
stats = new double[cluster.numServers];
11651209
}
@@ -1191,7 +1235,7 @@ static class PrimaryRegionCountSkewCostFunction extends CostFunction {
11911235
}
11921236

11931237
@Override
1194-
double cost() {
1238+
protected double cost() {
11951239
if (!cluster.hasRegionReplicas) {
11961240
return 0;
11971241
}
@@ -1228,7 +1272,7 @@ static class TableSkewCostFunction extends CostFunction {
12281272
}
12291273

12301274
@Override
1231-
double cost() {
1275+
protected double cost() {
12321276
double max = cluster.numRegions;
12331277
double min = ((double) cluster.numRegions) / cluster.numServers;
12341278
double value = 0;
@@ -1311,7 +1355,7 @@ protected void regionMoved(int region, int oldServer, int newServer) {
13111355
}
13121356

13131357
@Override
1314-
double cost() {
1358+
protected double cost() {
13151359
return 1 - locality;
13161360
}
13171361

@@ -1389,7 +1433,7 @@ void setLoads(Map<String, Deque<BalancerRegionLoad>> l) {
13891433
}
13901434

13911435
@Override
1392-
double cost() {
1436+
protected double cost() {
13931437
if (clusterStatus == null || loads == null) {
13941438
return 0;
13951439
}
@@ -1581,7 +1625,7 @@ boolean isNeeded() {
15811625
}
15821626

15831627
@Override
1584-
double cost() {
1628+
protected double cost() {
15851629
if (maxCost <= 0) {
15861630
return 0;
15871631
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 org.apache.hadoop.conf.Configuration;
21+
22+
public class DummyCostFunction extends StochasticLoadBalancer.CostFunction {
23+
public DummyCostFunction(Configuration c) {
24+
super(c);
25+
}
26+
27+
@Override
28+
protected double cost() {
29+
return 0;
30+
}
31+
}

hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestStochasticLoadBalancer.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static org.mockito.Mockito.when;
2626

2727
import java.util.ArrayList;
28+
import java.util.Arrays;
2829
import java.util.HashMap;
2930
import java.util.HashSet;
3031
import java.util.List;
@@ -121,7 +122,8 @@ public class TestStochasticLoadBalancer extends BalancerTestBase {
121122
};
122123

123124
private ServerMetrics mockServerMetricsWithCpRequests(ServerName server,
124-
List<RegionInfo> regionsOnServer, long cpRequestCount) {
125+
List<RegionInfo> regionsOnServer,
126+
long cpRequestCount) {
125127
ServerMetrics serverMetrics = mock(ServerMetrics.class);
126128
Map<byte[], RegionMetrics> regionLoadMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
127129
for(RegionInfo info : regionsOnServer){
@@ -457,6 +459,17 @@ public void testLosingRs() throws Exception {
457459
assertNull(plans);
458460
}
459461

462+
@Test
463+
public void testAdditionalCostFunction() {
464+
conf.set(StochasticLoadBalancer.COST_FUNCTIONS_COST_FUNCTIONS_KEY,
465+
DummyCostFunction.class.getName());
466+
467+
loadBalancer.setConf(conf);
468+
assertTrue(Arrays.
469+
asList(loadBalancer.getCostFunctionNames()).
470+
contains(DummyCostFunction.class.getSimpleName()));
471+
}
472+
460473
// This mock allows us to test the LocalityCostFunction
461474
private class MockCluster extends BaseLoadBalancer.Cluster {
462475

0 commit comments

Comments
 (0)