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 ;
@@ -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 }
0 commit comments