Skip to content

Commit 517c43b

Browse files
author
Ray Mattingly
committed
HBASE-28513 The StochasticLoadBalancer should support discrete evaluations
1 parent 5f312dd commit 517c43b

17 files changed

+1732
-26
lines changed

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@
3333
import org.apache.hadoop.hbase.client.RegionInfo;
3434
import org.apache.hadoop.hbase.client.RegionReplicaUtil;
3535
import org.apache.hadoop.hbase.master.RackManager;
36+
import org.apache.hadoop.hbase.master.RegionPlan;
3637
import org.apache.hadoop.hbase.net.Address;
3738
import org.apache.hadoop.hbase.util.Pair;
3839
import org.apache.yetus.audience.InterfaceAudience;
3940
import org.slf4j.Logger;
4041
import org.slf4j.LoggerFactory;
4142

43+
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
44+
4245
/**
4346
* An efficient array based implementation similar to ClusterState for keeping the status of the
4447
* cluster in terms of region assignment and distribution. LoadBalancers, such as
@@ -705,7 +708,7 @@ enum LocalityType {
705708
RACK
706709
}
707710

708-
public void doAction(BalanceAction action) {
711+
public List<RegionPlan> doAction(BalanceAction action) {
709712
switch (action.getType()) {
710713
case NULL:
711714
break;
@@ -715,30 +718,29 @@ public void doAction(BalanceAction action) {
715718
AssignRegionAction ar = (AssignRegionAction) action;
716719
regionsPerServer[ar.getServer()] =
717720
addRegion(regionsPerServer[ar.getServer()], ar.getRegion());
718-
regionMoved(ar.getRegion(), -1, ar.getServer());
719-
break;
721+
return ImmutableList.of(regionMoved(ar.getRegion(), -1, ar.getServer()));
720722
case MOVE_REGION:
721723
assert action instanceof MoveRegionAction : action.getClass();
722724
MoveRegionAction mra = (MoveRegionAction) action;
723725
regionsPerServer[mra.getFromServer()] =
724726
removeRegion(regionsPerServer[mra.getFromServer()], mra.getRegion());
725727
regionsPerServer[mra.getToServer()] =
726728
addRegion(regionsPerServer[mra.getToServer()], mra.getRegion());
727-
regionMoved(mra.getRegion(), mra.getFromServer(), mra.getToServer());
728-
break;
729+
return ImmutableList
730+
.of(regionMoved(mra.getRegion(), mra.getFromServer(), mra.getToServer()));
729731
case SWAP_REGIONS:
730732
assert action instanceof SwapRegionsAction : action.getClass();
731733
SwapRegionsAction a = (SwapRegionsAction) action;
732734
regionsPerServer[a.getFromServer()] =
733735
replaceRegion(regionsPerServer[a.getFromServer()], a.getFromRegion(), a.getToRegion());
734736
regionsPerServer[a.getToServer()] =
735737
replaceRegion(regionsPerServer[a.getToServer()], a.getToRegion(), a.getFromRegion());
736-
regionMoved(a.getFromRegion(), a.getFromServer(), a.getToServer());
737-
regionMoved(a.getToRegion(), a.getToServer(), a.getFromServer());
738-
break;
738+
return ImmutableList.of(regionMoved(a.getFromRegion(), a.getFromServer(), a.getToServer()),
739+
regionMoved(a.getToRegion(), a.getToServer(), a.getFromServer()));
739740
default:
740-
throw new RuntimeException("Uknown action:" + action.getType());
741+
throw new RuntimeException("Unknown action:" + action.getType());
741742
}
743+
return Collections.emptyList();
742744
}
743745

744746
/**
@@ -822,7 +824,7 @@ void doAssignRegion(RegionInfo regionInfo, ServerName serverName) {
822824
doAction(new AssignRegionAction(region, server));
823825
}
824826

825-
void regionMoved(int region, int oldServer, int newServer) {
827+
RegionPlan regionMoved(int region, int oldServer, int newServer) {
826828
regionIndexToServerIndex[region] = newServer;
827829
if (initialRegionIndexToServerIndex[region] == newServer) {
828830
numMovedRegions--; // region moved back to original location
@@ -853,6 +855,11 @@ void regionMoved(int region, int oldServer, int newServer) {
853855
updateForLocation(serverIndexToRackIndex, regionsPerRack, colocatedReplicaCountsPerRack,
854856
oldServer, newServer, primary, region);
855857
}
858+
859+
// old server name can be null
860+
ServerName oldServerName = oldServer == -1 ? null : servers[oldServer];
861+
862+
return new RegionPlan(regions[region], oldServerName, servers[newServer]);
856863
}
857864

858865
/**
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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 java.lang.reflect.Constructor;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Set;
24+
import java.util.concurrent.atomic.AtomicInteger;
25+
import java.util.stream.Collectors;
26+
import org.apache.hadoop.conf.Configuration;
27+
import org.apache.hadoop.hbase.master.RegionPlan;
28+
import org.apache.hadoop.hbase.util.ReflectionUtils;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
32+
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableSet;
33+
34+
/**
35+
* Balancer conditionals supplement cost functions in the {@link StochasticLoadBalancer}. Cost
36+
* functions are insufficient and difficult to work with when making discrete decisions; this is
37+
* because they operate on a continuous scale, and each cost function's multiplier affects the
38+
* relative importance of every other cost function. So it is difficult to meaningfully and clearly
39+
* value many aspects of your region distribution via cost functions alone. Conditionals allow you
40+
* to very clearly define discrete rules that your balancer would ideally follow. To clarify, a
41+
* conditional violation will not block a region assignment because we would prefer to have uptime
42+
* than have perfectly intentional balance. But conditionals allow you to, for example, define that
43+
* a region's primary and secondary should not live on the same rack. Another example, conditionals
44+
* make it easy to define that system tables will ideally be isolated on their own RegionServer.
45+
*/
46+
public class BalancerConditionals {
47+
48+
private static final Logger LOG = LoggerFactory.getLogger(BalancerConditionals.class);
49+
50+
protected static BalancerConditionals INSTANCE = new BalancerConditionals();
51+
public static final String ISOLATE_SYSTEM_TABLES_KEY =
52+
"hbase.master.balancer.stochastic.conditionals.isolateSystemTables";
53+
public static final boolean ISOLATE_SYSTEM_TABLES_DEFAULT = false;
54+
55+
public static final String ISOLATE_META_TABLE_KEY =
56+
"hbase.master.balancer.stochastic.conditionals.isolateMetaTable";
57+
public static final boolean ISOLATE_META_TABLE_DEFAULT = false;
58+
59+
public static final String DISTRIBUTE_REPLICAS_CONDITIONALS_KEY =
60+
"hbase.master.balancer.stochastic.conditionals.distributeReplicas";
61+
public static final boolean DISTRIBUTE_REPLICAS_CONDITIONALS_DEFAULT = false;
62+
63+
public static final String ADDITIONAL_CONDITIONALS_KEY =
64+
"hbase.master.balancer.stochastic.additionalConditionals";
65+
66+
// when this count is low, we'll be more likely to trigger a subsequent balancer run
67+
private static final AtomicInteger BALANCE_COUNT_WITHOUT_IMPROVEMENTS = new AtomicInteger(0);
68+
private static final int BALANCE_COUNT_WITHOUT_IMPROVEMENTS_CEILING = 10;
69+
70+
private Set<Class<? extends RegionPlanConditional>> conditionalClasses = Collections.emptySet();
71+
private Set<RegionPlanConditional> conditionals = Collections.emptySet();
72+
private Configuration conf;
73+
74+
private BalancerConditionals() {
75+
}
76+
77+
protected boolean isTableIsolationEnabled() {
78+
return conditionalClasses.contains(SystemTableIsolationConditional.class)
79+
|| conditionalClasses.contains(MetaTableIsolationConditional.class);
80+
}
81+
82+
protected boolean shouldRunBalancer() {
83+
return BALANCE_COUNT_WITHOUT_IMPROVEMENTS.get() < BALANCE_COUNT_WITHOUT_IMPROVEMENTS_CEILING;
84+
}
85+
86+
protected int getConsecutiveBalancesWithoutImprovement() {
87+
return BALANCE_COUNT_WITHOUT_IMPROVEMENTS.get();
88+
}
89+
90+
protected void incConsecutiveBalancesWithoutImprovement() {
91+
if (BALANCE_COUNT_WITHOUT_IMPROVEMENTS.get() == Integer.MAX_VALUE) {
92+
return;
93+
}
94+
this.BALANCE_COUNT_WITHOUT_IMPROVEMENTS.getAndIncrement();
95+
LOG.trace("Set balanceCountWithoutImprovements={}",
96+
this.BALANCE_COUNT_WITHOUT_IMPROVEMENTS.get());
97+
}
98+
99+
protected void resetConsecutiveBalancesWithoutImprovement() {
100+
this.BALANCE_COUNT_WITHOUT_IMPROVEMENTS.set(0);
101+
LOG.trace("Set balanceCountWithoutImprovements=0");
102+
}
103+
104+
protected Set<Class<? extends RegionPlanConditional>> getConditionalClasses() {
105+
return Set.copyOf(conditionalClasses);
106+
}
107+
108+
protected boolean shouldSkipSloppyServerEvaluation() {
109+
return conditionals.stream()
110+
.anyMatch(conditional -> conditional instanceof SystemTableIsolationConditional
111+
|| conditional instanceof MetaTableIsolationConditional);
112+
}
113+
114+
protected void loadConf(Configuration conf) {
115+
this.conf = conf;
116+
ImmutableSet.Builder<Class<? extends RegionPlanConditional>> conditionalClasses =
117+
ImmutableSet.builder();
118+
119+
boolean isolateSystemTables =
120+
conf.getBoolean(ISOLATE_SYSTEM_TABLES_KEY, ISOLATE_SYSTEM_TABLES_DEFAULT);
121+
if (isolateSystemTables) {
122+
conditionalClasses.add(SystemTableIsolationConditional.class);
123+
}
124+
125+
boolean isolateMetaTable = conf.getBoolean(ISOLATE_META_TABLE_KEY, ISOLATE_META_TABLE_DEFAULT);
126+
if (isolateMetaTable) {
127+
conditionalClasses.add(MetaTableIsolationConditional.class);
128+
}
129+
130+
boolean distributeReplicas = conf.getBoolean(DISTRIBUTE_REPLICAS_CONDITIONALS_KEY,
131+
DISTRIBUTE_REPLICAS_CONDITIONALS_DEFAULT);
132+
if (distributeReplicas) {
133+
conditionalClasses.add(DistributeReplicasConditional.class);
134+
}
135+
136+
Class<?>[] classes = conf.getClasses(ADDITIONAL_CONDITIONALS_KEY);
137+
for (Class<?> clazz : classes) {
138+
if (!RegionPlanConditional.class.isAssignableFrom(clazz)) {
139+
LOG.warn("Class {} is not a RegionPlanConditional", clazz.getName());
140+
continue;
141+
}
142+
conditionalClasses.add(clazz.asSubclass(RegionPlanConditional.class));
143+
}
144+
this.conditionalClasses = conditionalClasses.build();
145+
}
146+
147+
protected void loadClusterState(BalancerClusterState cluster) {
148+
conditionals = conditionalClasses.stream().map(clazz -> createConditional(clazz, conf, cluster))
149+
.collect(Collectors.toSet());
150+
}
151+
152+
protected int getConditionalViolationChange(List<RegionPlan> regionPlans) {
153+
if (conditionals.isEmpty()) {
154+
incConsecutiveBalancesWithoutImprovement();
155+
return 0;
156+
}
157+
int conditionalViolationChange = 0;
158+
for (RegionPlan regionPlan : regionPlans) {
159+
conditionalViolationChange += getConditionalViolationChange(conditionals, regionPlan);
160+
}
161+
return conditionalViolationChange;
162+
}
163+
164+
private static int getConditionalViolationChange(Set<RegionPlanConditional> conditionals,
165+
RegionPlan regionPlan) {
166+
RegionPlan inverseRegionPlan = new RegionPlan(regionPlan.getRegionInfo(),
167+
regionPlan.getDestination(), regionPlan.getSource());
168+
int currentConditionalViolationCount =
169+
getConditionalViolationCount(conditionals, inverseRegionPlan);
170+
int newConditionalViolationCount = getConditionalViolationCount(conditionals, regionPlan);
171+
int violationChange = newConditionalViolationCount - currentConditionalViolationCount;
172+
if (violationChange < 0) {
173+
LOG.trace("Should move region {}_{} from {} to {}", regionPlan.getRegionName(),
174+
regionPlan.getRegionInfo().getReplicaId(), regionPlan.getSource().getServerName(),
175+
regionPlan.getDestination().getServerName());
176+
}
177+
return violationChange;
178+
}
179+
180+
private static int getConditionalViolationCount(Set<RegionPlanConditional> conditionals,
181+
RegionPlan regionPlan) {
182+
int regionPlanConditionalViolationCount = 0;
183+
for (RegionPlanConditional regionPlanConditional : conditionals) {
184+
if (regionPlanConditional.isViolating(regionPlan)) {
185+
regionPlanConditionalViolationCount++;
186+
}
187+
}
188+
return regionPlanConditionalViolationCount;
189+
}
190+
191+
private RegionPlanConditional createConditional(Class<? extends RegionPlanConditional> clazz,
192+
Configuration conf, BalancerClusterState cluster) {
193+
try {
194+
Constructor<? extends RegionPlanConditional> ctor =
195+
clazz.getDeclaredConstructor(Configuration.class, BalancerClusterState.class);
196+
return ReflectionUtils.instantiate(clazz.getName(), ctor, conf, cluster);
197+
} catch (NoSuchMethodException e) {
198+
LOG.warn(
199+
"Cannot find constructor with Configuration and BalancerClusterState parameters for class '{}': {}",
200+
clazz.getName(), e.getMessage());
201+
}
202+
return null;
203+
}
204+
}

0 commit comments

Comments
 (0)