Skip to content

Commit a18be19

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

File tree

3 files changed

+123
-35
lines changed

3 files changed

+123
-35
lines changed

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

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

218263
protected void setCandidateGenerators(List<CandidateGenerator> customCandidateGenerators) {
@@ -466,8 +511,8 @@ private void updateStochasticCosts(TableName tableName, Double overall, Double[]
466511
"Overall", "Overall cost", overall);
467512

468513
// each cost function
469-
for (int i = 0; i < costFunctions.length; i++) {
470-
CostFunction costFunction = costFunctions[i];
514+
for (int i = 0; i < costFunctions.size(); i++) {
515+
CostFunction costFunction = costFunctions.get(i);
471516
String costFunctionName = costFunction.getClass().getSimpleName();
472517
Double costPercent = (overall == 0) ? 0 : (subCosts[i] / overall);
473518
// TODO: cost function may need a specific description
@@ -565,9 +610,9 @@ protected void updateCostsWithAction(Cluster cluster, Action action) {
565610
*/
566611
public String[] getCostFunctionNames() {
567612
if (costFunctions == null) return null;
568-
String[] ret = new String[costFunctions.length];
569-
for (int i = 0; i < costFunctions.length; i++) {
570-
CostFunction c = costFunctions[i];
613+
String[] ret = new String[costFunctions.size()];
614+
for (int i = 0; i < costFunctions.size(); i++) {
615+
CostFunction c = costFunctions.get(i);
571616
ret[i] = c.getClass().getSimpleName();
572617
}
573618

@@ -586,8 +631,8 @@ public String[] getCostFunctionNames() {
586631
protected double computeCost(Cluster cluster, double previousCost) {
587632
double total = 0;
588633

589-
for (int i = 0; i < costFunctions.length; i++) {
590-
CostFunction c = costFunctions[i];
634+
for (int i = 0; i < costFunctions.size(); i++) {
635+
CostFunction c = costFunctions.get(i);
591636
this.tempFunctionCosts[i] = 0.0;
592637

593638
if (c.getMultiplier() <= 0) {
@@ -970,13 +1015,13 @@ Cluster.Action generate(Cluster cluster) {
9701015
/**
9711016
* Base class of StochasticLoadBalancer's Cost Functions.
9721017
*/
973-
abstract static class CostFunction {
1018+
public abstract static class CostFunction {
9741019

9751020
private float multiplier = 0;
9761021

9771022
protected Cluster cluster;
9781023

979-
CostFunction(Configuration c) {
1024+
public CostFunction(Configuration c) {
9801025
}
9811026

9821027
boolean isNeeded() {
@@ -1025,7 +1070,7 @@ void postAction(Action action) {
10251070
protected void regionMoved(int region, int oldServer, int newServer) {
10261071
}
10271072

1028-
abstract double cost();
1073+
protected abstract double cost();
10291074

10301075
@SuppressWarnings("checkstyle:linelength")
10311076
/**
@@ -1122,7 +1167,7 @@ static class MoveCostFunction extends CostFunction {
11221167
}
11231168

11241169
@Override
1125-
double cost() {
1170+
protected double cost() {
11261171
// Try and size the max number of Moves, but always be prepared to move some.
11271172
int maxMoves = Math.max((int) (cluster.numRegions * maxMovesPercent),
11281173
DEFAULT_MAX_MOVES);
@@ -1157,7 +1202,7 @@ static class RegionCountSkewCostFunction extends CostFunction {
11571202
}
11581203

11591204
@Override
1160-
double cost() {
1205+
protected double cost() {
11611206
if (stats == null || stats.length != cluster.numServers) {
11621207
stats = new double[cluster.numServers];
11631208
}
@@ -1189,7 +1234,7 @@ static class PrimaryRegionCountSkewCostFunction extends CostFunction {
11891234
}
11901235

11911236
@Override
1192-
double cost() {
1237+
protected double cost() {
11931238
if (!cluster.hasRegionReplicas) {
11941239
return 0;
11951240
}
@@ -1226,7 +1271,7 @@ static class TableSkewCostFunction extends CostFunction {
12261271
}
12271272

12281273
@Override
1229-
double cost() {
1274+
protected double cost() {
12301275
double max = cluster.numRegions;
12311276
double min = ((double) cluster.numRegions) / cluster.numServers;
12321277
double value = 0;
@@ -1309,7 +1354,7 @@ protected void regionMoved(int region, int oldServer, int newServer) {
13091354
}
13101355

13111356
@Override
1312-
double cost() {
1357+
protected double cost() {
13131358
return 1 - locality;
13141359
}
13151360

@@ -1387,7 +1432,7 @@ void setLoads(Map<String, Deque<BalancerRegionLoad>> l) {
13871432
}
13881433

13891434
@Override
1390-
double cost() {
1435+
protected double cost() {
13911436
if (clusterStatus == null || loads == null) {
13921437
return 0;
13931438
}
@@ -1557,7 +1602,7 @@ boolean isNeeded() {
15571602
}
15581603

15591604
@Override
1560-
double cost() {
1605+
protected double cost() {
15611606
if (maxCost <= 0) {
15621607
return 0;
15631608
}
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: 12 additions & 0 deletions
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.List;
2930
import java.util.Map;
3031
import java.util.Queue;
@@ -370,6 +371,17 @@ public void testLosingRs() throws Exception {
370371
assertNull(plans);
371372
}
372373

374+
@Test
375+
public void testAdditionalCostFunction() {
376+
conf.set(StochasticLoadBalancer.COST_FUNCTIONS_COST_FUNCTIONS_KEY,
377+
DummyCostFunction.class.getName());
378+
379+
loadBalancer.setConf(conf);
380+
assertTrue(Arrays.
381+
asList(loadBalancer.getCostFunctionNames()).
382+
contains(DummyCostFunction.class.getSimpleName()));
383+
}
384+
373385
// This mock allows us to test the LocalityCostFunction
374386
private class MockCluster extends BaseLoadBalancer.Cluster {
375387

0 commit comments

Comments
 (0)