2727import java .util .LinkedList ;
2828import java .util .List ;
2929import java .util .Map ;
30+ import java .util .Objects ;
3031import java .util .Random ;
32+ import java .util .stream .Collectors ;
3133
3234import org .apache .hadoop .conf .Configuration ;
3335import org .apache .hadoop .hbase .ClusterMetrics ;
4749import org .apache .hadoop .hbase .master .balancer .BaseLoadBalancer .Cluster .MoveRegionAction ;
4850import org .apache .hadoop .hbase .master .balancer .BaseLoadBalancer .Cluster .SwapRegionsAction ;
4951import org .apache .hadoop .hbase .util .EnvironmentEdgeManager ;
52+ import org .apache .hadoop .hbase .util .ReflectionUtils ;
5053import org .apache .yetus .audience .InterfaceAudience ;
5154import org .slf4j .Logger ;
5255import org .slf4j .LoggerFactory ;
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 }
0 commit comments