Skip to content

Commit 860d8b7

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

18 files changed

+1976
-24
lines changed

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

Lines changed: 25 additions & 14 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,33 @@ 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;
723-
regionsPerServer[mra.getFromServer()] =
724-
removeRegion(regionsPerServer[mra.getFromServer()], mra.getRegion());
725-
regionsPerServer[mra.getToServer()] =
726-
addRegion(regionsPerServer[mra.getToServer()], mra.getRegion());
727-
regionMoved(mra.getRegion(), mra.getFromServer(), mra.getToServer());
728-
break;
725+
try {
726+
regionsPerServer[mra.getFromServer()] =
727+
removeRegion(regionsPerServer[mra.getFromServer()], mra.getRegion());
728+
regionsPerServer[mra.getToServer()] =
729+
addRegion(regionsPerServer[mra.getToServer()], mra.getRegion());
730+
return ImmutableList
731+
.of(regionMoved(mra.getRegion(), mra.getFromServer(), mra.getToServer()));
732+
} catch (Exception e) {
733+
throw e;
734+
}
729735
case SWAP_REGIONS:
730736
assert action instanceof SwapRegionsAction : action.getClass();
731737
SwapRegionsAction a = (SwapRegionsAction) action;
732738
regionsPerServer[a.getFromServer()] =
733739
replaceRegion(regionsPerServer[a.getFromServer()], a.getFromRegion(), a.getToRegion());
734740
regionsPerServer[a.getToServer()] =
735741
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;
742+
return ImmutableList.of(regionMoved(a.getFromRegion(), a.getFromServer(), a.getToServer()),
743+
regionMoved(a.getToRegion(), a.getToServer(), a.getFromServer()));
739744
default:
740-
throw new RuntimeException("Uknown action:" + action.getType());
745+
throw new RuntimeException("Unknown action:" + action.getType());
741746
}
747+
return Collections.emptyList();
742748
}
743749

744750
/**
@@ -822,7 +828,7 @@ void doAssignRegion(RegionInfo regionInfo, ServerName serverName) {
822828
doAction(new AssignRegionAction(region, server));
823829
}
824830

825-
void regionMoved(int region, int oldServer, int newServer) {
831+
RegionPlan regionMoved(int region, int oldServer, int newServer) {
826832
regionIndexToServerIndex[region] = newServer;
827833
if (initialRegionIndexToServerIndex[region] == newServer) {
828834
numMovedRegions--; // region moved back to original location
@@ -853,6 +859,11 @@ void regionMoved(int region, int oldServer, int newServer) {
853859
updateForLocation(serverIndexToRackIndex, regionsPerRack, colocatedReplicaCountsPerRack,
854860
oldServer, newServer, primary, region);
855861
}
862+
863+
// old server name can be null
864+
ServerName oldServerName = oldServer == -1 ? null : servers[oldServer];
865+
866+
return new RegionPlan(regions[region], oldServerName, servers[newServer]);
856867
}
857868

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

0 commit comments

Comments
 (0)