Skip to content

Commit

Permalink
xds: implement WeightedTargetLoadBalancer
Browse files Browse the repository at this point in the history
  • Loading branch information
dapengzhang0 committed Mar 11, 2020
1 parent 3b8e363 commit 5e7b8c6
Show file tree
Hide file tree
Showing 9 changed files with 959 additions and 100 deletions.
23 changes: 2 additions & 21 deletions xds/src/main/java/io/grpc/xds/LocalityStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,12 @@
import io.grpc.xds.EnvoyProtoData.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.InterLocalityPicker.WeightedChildPicker;
import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig;
import io.grpc.xds.OrcaOobUtil.OrcaReportingHelperWrapper;
import io.grpc.xds.WeightedRandomPicker.WeightedChildPicker;
import io.grpc.xds.XdsLogger.XdsLogLevel;
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -109,7 +108,6 @@ final class LocalityStoreImpl implements LocalityStore {

private final XdsLogger logger;
private final Helper helper;
private final PickerFactory pickerFactory;
private final LoadBalancerProvider loadBalancerProvider;
private final ThreadSafeRandom random;
private final LoadStatsStore loadStatsStore;
Expand All @@ -130,7 +128,6 @@ final class LocalityStoreImpl implements LocalityStore {
this(
logId,
helper,
pickerFactoryImpl,
lbRegistry,
ThreadSafeRandom.ThreadSafeRandomImpl.instance,
loadStatsStore,
Expand All @@ -142,14 +139,12 @@ final class LocalityStoreImpl implements LocalityStore {
LocalityStoreImpl(
InternalLogId logId,
Helper helper,
PickerFactory pickerFactory,
LoadBalancerRegistry lbRegistry,
ThreadSafeRandom random,
LoadStatsStore loadStatsStore,
OrcaPerRequestUtil orcaPerRequestUtil,
OrcaOobUtil orcaOobUtil) {
this.helper = checkNotNull(helper, "helper");
this.pickerFactory = checkNotNull(pickerFactory, "pickerFactory");
loadBalancerProvider = checkNotNull(
lbRegistry.getProvider(ROUND_ROBIN),
"Unable to find '%s' LoadBalancer", ROUND_ROBIN);
Expand All @@ -160,11 +155,6 @@ final class LocalityStoreImpl implements LocalityStore {
logger = XdsLogger.withLogId(checkNotNull(logId, "logId"));
}

@VisibleForTesting // Introduced for testing only.
interface PickerFactory {
SubchannelPicker picker(List<WeightedChildPicker> childPickers);
}

private final class DroppablePicker extends SubchannelPicker {

final List<DropOverload> dropOverloads;
Expand Down Expand Up @@ -206,14 +196,6 @@ public String toString() {
}
}

private static final PickerFactory pickerFactoryImpl =
new PickerFactory() {
@Override
public SubchannelPicker picker(List<WeightedChildPicker> childPickers) {
return new InterLocalityPicker(childPickers);
}
};

@Override
public void reset() {
for (Locality locality : localityMap.keySet()) {
Expand Down Expand Up @@ -335,7 +317,6 @@ private static ConnectivityState aggregateState(

private void updatePicker(
@Nullable ConnectivityState state, List<WeightedChildPicker> childPickers) {
childPickers = Collections.unmodifiableList(childPickers);
SubchannelPicker picker;
if (childPickers.isEmpty()) {
if (state == TRANSIENT_FAILURE) {
Expand All @@ -344,7 +325,7 @@ private void updatePicker(
picker = XdsSubchannelPickers.BUFFER_PICKER;
}
} else {
picker = pickerFactory.picker(childPickers);
picker = new WeightedRandomPicker(childPickers);
}

if (!dropOverloads.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.PickSubchannelArgs;
import io.grpc.LoadBalancer.SubchannelPicker;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

final class InterLocalityPicker extends SubchannelPicker {
final class WeightedRandomPicker extends SubchannelPicker {

@VisibleForTesting
final List<WeightedChildPicker> weightedChildPickers;

private final List<WeightedChildPicker> weightedChildPickers;
private final ThreadSafeRandom random;
private final int totalWeight;

static final class WeightedChildPicker {
final int weight;
final SubchannelPicker childPicker;
private final int weight;
private final SubchannelPicker childPicker;

WeightedChildPicker(int weight, SubchannelPicker childPicker) {
checkArgument(weight >= 0, "weight is negative");
Expand All @@ -53,6 +56,23 @@ SubchannelPicker getPicker() {
return childPicker;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
WeightedChildPicker that = (WeightedChildPicker) o;
return weight == that.weight && Objects.equals(childPicker, that.childPicker);
}

@Override
public int hashCode() {
return Objects.hash(weight, childPicker);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
Expand All @@ -62,16 +82,16 @@ public String toString() {
}
}

InterLocalityPicker(List<WeightedChildPicker> weightedChildPickers) {
WeightedRandomPicker(List<WeightedChildPicker> weightedChildPickers) {
this(weightedChildPickers, ThreadSafeRandom.ThreadSafeRandomImpl.instance);
}

@VisibleForTesting
InterLocalityPicker(List<WeightedChildPicker> weightedChildPickers, ThreadSafeRandom random) {
WeightedRandomPicker(List<WeightedChildPicker> weightedChildPickers, ThreadSafeRandom random) {
checkNotNull(weightedChildPickers, "weightedChildPickers in null");
checkArgument(!weightedChildPickers.isEmpty(), "weightedChildPickers is empty");

this.weightedChildPickers = ImmutableList.copyOf(weightedChildPickers);
this.weightedChildPickers = Collections.unmodifiableList(weightedChildPickers);

int totalWeight = 0;
for (WeightedChildPicker weightedChildPicker : weightedChildPickers) {
Expand Down
193 changes: 193 additions & 0 deletions xds/src/main/java/io/grpc/xds/WeightedTargetLoadBalancer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright 2020 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.grpc.xds;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.ConnectivityState.CONNECTING;
import static io.grpc.ConnectivityState.IDLE;
import static io.grpc.ConnectivityState.READY;
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;

import com.google.common.collect.ImmutableMap;
import io.grpc.ConnectivityState;
import io.grpc.InternalLogId;
import io.grpc.LoadBalancer;
import io.grpc.Status;
import io.grpc.util.ForwardingLoadBalancerHelper;
import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.xds.WeightedRandomPicker.WeightedChildPicker;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
import io.grpc.xds.XdsLogger.XdsLogLevel;
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/** Load balancer for weighted_target policy. */
final class WeightedTargetLoadBalancer extends LoadBalancer {

private final XdsLogger logger;
private final Map<String, GracefulSwitchLoadBalancer> childBalancers = new HashMap<>();
private final Map<String, ChildHelper> childHelpers = new HashMap<>();
private final Helper helper;

private Map<String, WeightedPolicySelection> targets = ImmutableMap.of();

WeightedTargetLoadBalancer(Helper helper) {
this.helper = helper;
logger = XdsLogger.withLogId(
InternalLogId.allocate("weighted-target-lb", helper.getAuthority()));
logger.log(XdsLogLevel.INFO, "Created");
}

@Override
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses);
Object lbConfig = resolvedAddresses.getLoadBalancingPolicyConfig();
checkNotNull(lbConfig, "missing weighted_target lb config");

WeightedTargetConfig weightedTargetConfig = (WeightedTargetConfig) lbConfig;
Map<String, WeightedPolicySelection> newTargets = weightedTargetConfig.targets;

for (String targetName : newTargets.keySet()) {
WeightedPolicySelection weightedChildLbConfig = newTargets.get(targetName);
if (!targets.containsKey(targetName)) {
ChildHelper childHelper = new ChildHelper();
GracefulSwitchLoadBalancer childBalancer = new GracefulSwitchLoadBalancer(childHelper);
childBalancer.switchTo(weightedChildLbConfig.policySelection.getProvider());
childHelpers.put(targetName, childHelper);
childBalancers.put(targetName, childBalancer);
} else if (!weightedChildLbConfig.policySelection.getProvider().equals(
targets.get(targetName).policySelection.getProvider())) {
childBalancers.get(targetName)
.switchTo(weightedChildLbConfig.policySelection.getProvider());
}
}

targets = newTargets;

for (String targetName : targets.keySet()) {
childBalancers.get(targetName).handleResolvedAddresses(
resolvedAddresses.toBuilder()
.setLoadBalancingPolicyConfig(targets.get(targetName).policySelection.getConfig())
.build());
}

// Cleanup removed targets.
// TODO(zdapeng): cache removed target for 15 minutes.
for (String targetName : childBalancers.keySet()) {
if (!targets.containsKey(targetName)) {
childBalancers.get(targetName).shutdown();
}
}
childBalancers.keySet().retainAll(targets.keySet());
childHelpers.keySet().retainAll(targets.keySet());
}

@Override
public void handleNameResolutionError(Status error) {
logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error);
if (childBalancers.isEmpty()) {
helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(error));
}
for (LoadBalancer childBalancer : childBalancers.values()) {
childBalancer.handleNameResolutionError(error);
}
}

@Override
public boolean canHandleEmptyAddressListFromNameResolution() {
return true;
}

@Override
public void shutdown() {
logger.log(XdsLogLevel.INFO, "Shutdown");
for (LoadBalancer childBalancer : childBalancers.values()) {
childBalancer.shutdown();
}
}

private void updateOverallBalancingState() {
List<WeightedChildPicker> childPickers = new ArrayList<>();

ConnectivityState overallState = null;
for (String name : targets.keySet()) {
ChildHelper childHelper = childHelpers.get(name);
ConnectivityState childState = childHelper.currentState;
overallState = aggregateState(overallState, childState);
if (READY == childState) {
int weight = targets.get(name).weight;
childPickers.add(new WeightedChildPicker(weight, childHelper.currentPicker));
}
}

SubchannelPicker picker;
if (childPickers.isEmpty()) {
if (overallState == TRANSIENT_FAILURE) {
picker = new ErrorPicker(Status.UNAVAILABLE); // TODO: more details in status
} else {
picker = XdsSubchannelPickers.BUFFER_PICKER;
}
} else {
picker = new WeightedRandomPicker(childPickers);
}

if (overallState != null) {
helper.updateBalancingState(overallState, picker);
}
}

@Nullable
private ConnectivityState aggregateState(
@Nullable ConnectivityState overallState, ConnectivityState childState) {
if (overallState == null) {
return childState;
}
if (overallState == READY || childState == READY) {
return READY;
}
if (overallState == CONNECTING || childState == CONNECTING) {
return CONNECTING;
}
if (overallState == IDLE || childState == IDLE) {
return IDLE;
}
return overallState;
}

private final class ChildHelper extends ForwardingLoadBalancerHelper {
ConnectivityState currentState = CONNECTING;
SubchannelPicker currentPicker = BUFFER_PICKER;

@Override
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
currentState = newState;
currentPicker = newPicker;
updateOverallBalancingState();
}

@Override
protected Helper delegate() {
return helper;
}
}
}

0 comments on commit 5e7b8c6

Please sign in to comment.