forked from grpc/grpc-java
-
Notifications
You must be signed in to change notification settings - Fork 0
subsetting java impl #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
joybestourous
wants to merge
25
commits into
master
Choose a base branch
from
subsetting
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
477f051
API, Provider, but no full impl yet
joybestourous f1dd2f8
subset implementation
joybestourous 4058b5b
provider test
joybestourous 5782386
suppresswarnings
joybestourous 2276082
LBRT
joybestourous 10fdbac
testing
joybestourous 6962789
small fixes
joybestourous 944d80f
remove DeterministicSubsettingSubchannel
joybestourous ac6bddf
remove ChildHelper
joybestourous b7869c4
comments, maxDiff
joybestourous ed96951
one eag per addy
joybestourous 3f9a619
static
joybestourous 9260f50
more errors
joybestourous ab4ff7a
checkstyle
joybestourous 8e8aa9b
fix test
joybestourous 5a9e169
checkstyle
joybestourous 1751776
checkstyle
joybestourous e31cd3d
use toString
joybestourous 182e5f7
use plugin
joybestourous 0bfd074
if {}
joybestourous 7fdcfc9
final fixes
joybestourous 76d686b
cover uncovered lines with tests
joybestourous 706c6f0
formatting
joybestourous ab30f97
checkstyle
joybestourous 02433be
stray space
joybestourous File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
util/src/main/java/io/grpc/util/DeterministicSubsettingLoadBalancer.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,210 @@ | ||
| /* | ||
| * Copyright 2023 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.util; | ||
|
|
||
| import static com.google.common.base.Preconditions.checkArgument; | ||
| import static com.google.common.base.Preconditions.checkNotNull; | ||
| import static com.google.common.base.Preconditions.checkState; | ||
|
|
||
| import com.google.common.annotations.VisibleForTesting; | ||
| import io.grpc.EquivalentAddressGroup; | ||
| import io.grpc.Internal; | ||
| import io.grpc.LoadBalancer; | ||
| import io.grpc.Status; | ||
| import io.grpc.internal.ServiceConfigUtil.PolicySelection; | ||
| import java.net.SocketAddress; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.Comparator; | ||
| import java.util.List; | ||
| import java.util.Random; | ||
|
|
||
| /** | ||
| * Wraps a child {@code LoadBalancer}, separating the total set of backends into smaller subsets for | ||
| * the child balancer to balance across. | ||
| * | ||
| * <p>This implements deterministic subsetting gRFC: | ||
| * https://github.com/grpc/proposal/blob/master/A68-deterministic-subsetting-lb-policy.md | ||
| */ | ||
| @Internal | ||
| public final class DeterministicSubsettingLoadBalancer extends LoadBalancer { | ||
|
|
||
| private final GracefulSwitchLoadBalancer switchLb; | ||
|
|
||
| @Override | ||
| public boolean acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { | ||
| DeterministicSubsettingLoadBalancerConfig config = | ||
| (DeterministicSubsettingLoadBalancerConfig) | ||
| resolvedAddresses.getLoadBalancingPolicyConfig(); | ||
|
|
||
| switchLb.switchTo(config.childPolicy.getProvider()); | ||
|
|
||
| ResolvedAddresses subsetAddresses = buildSubsets(resolvedAddresses, config); | ||
|
|
||
| switchLb.handleResolvedAddresses( | ||
| subsetAddresses.toBuilder() | ||
| .setLoadBalancingPolicyConfig(config.childPolicy.getConfig()) | ||
| .build()); | ||
| return true; | ||
| } | ||
|
|
||
| // implements the subsetting algorithm, as described in A68: | ||
| // https://github.com/grpc/proposal/pull/383 | ||
| private ResolvedAddresses buildSubsets( | ||
| ResolvedAddresses allAddresses, DeterministicSubsettingLoadBalancerConfig config) { | ||
| // The map should only retain entries for addresses in this latest update. | ||
| ArrayList<SocketAddress> addresses = new ArrayList<>(); | ||
| for (EquivalentAddressGroup addressGroup : allAddresses.getAddresses()) { | ||
| addresses.addAll(addressGroup.getAddresses()); | ||
| } | ||
|
|
||
| if (addresses.size() <= config.subsetSize) { | ||
| return allAddresses; | ||
| } | ||
| if (config.sortAddresses) { | ||
| // If we sort, we do so via the string representation of the SocketAddress. | ||
| addresses.sort(new AddressComparator()); | ||
| } | ||
|
|
||
| Integer backendCount = addresses.size(); | ||
| Integer subsetCount = backendCount / config.subsetSize; | ||
|
|
||
| Integer round = config.clientIndex / subsetCount; | ||
|
|
||
| Integer excludedCount = backendCount % config.subsetSize; | ||
| Integer excludedStart = (round * excludedCount) % backendCount; | ||
| Integer excludedEnd = (excludedStart + excludedCount) % backendCount; | ||
| if (excludedStart <= excludedEnd) { | ||
| List<SocketAddress> subList = addresses.subList(0, excludedStart); | ||
| subList.addAll(addresses.subList(excludedEnd, backendCount)); | ||
| addresses = new ArrayList<>(subList); | ||
| } else { | ||
| addresses = new ArrayList<>(addresses.subList(excludedEnd, excludedStart)); | ||
| } | ||
|
|
||
| Random r = new Random(round); | ||
| Collections.shuffle(addresses, r); | ||
|
|
||
| Integer subsetId = config.clientIndex % subsetCount; | ||
|
|
||
| Integer start = subsetId * config.subsetSize; | ||
| Integer end = start + config.subsetSize; | ||
|
|
||
| List<SocketAddress> subset = addresses.subList(start, end); | ||
|
|
||
| ArrayList<EquivalentAddressGroup> eaglist = new ArrayList<>(); | ||
|
|
||
| // Create new EAGs per address | ||
| for (SocketAddress addr : subset) { | ||
| eaglist.add(new EquivalentAddressGroup(addr)); | ||
| } | ||
|
|
||
| ResolvedAddresses.Builder builder = allAddresses.toBuilder(); | ||
| return builder.setAddresses(eaglist).build(); | ||
| } | ||
|
|
||
| @Override | ||
| public void handleNameResolutionError(Status error) { | ||
| switchLb.handleNameResolutionError(error); | ||
| } | ||
|
|
||
| @Override | ||
| public void shutdown() { | ||
| switchLb.shutdown(); | ||
| } | ||
|
|
||
| public DeterministicSubsettingLoadBalancer(Helper helper) { | ||
| switchLb = new GracefulSwitchLoadBalancer(checkNotNull(helper, "helper")); | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| static class AddressComparator implements Comparator<SocketAddress> { | ||
| // For consistency with the golang instrumentation, this assumes toString is overridden such | ||
| // that it is a string representation of an IP. Though any string representation of a | ||
| // SocketAddress will work here, other definitions of toString may yield differing results from | ||
| // the golang instrumentation. | ||
| @Override | ||
| public int compare(SocketAddress o1, SocketAddress o2) { | ||
| return o1.toString().compareTo(o2.toString()); | ||
| } | ||
| } | ||
|
|
||
| public static final class DeterministicSubsettingLoadBalancerConfig { | ||
|
|
||
| public final Integer clientIndex; | ||
| public final Integer subsetSize; | ||
| public final Boolean sortAddresses; | ||
|
|
||
| public final PolicySelection childPolicy; | ||
|
|
||
| private DeterministicSubsettingLoadBalancerConfig( | ||
| Integer clientIndex, | ||
| Integer subsetSize, | ||
| Boolean sortAddresses, | ||
| PolicySelection childPolicy) { | ||
| this.clientIndex = clientIndex; | ||
| this.subsetSize = subsetSize; | ||
| this.sortAddresses = sortAddresses; | ||
| this.childPolicy = childPolicy; | ||
| } | ||
|
|
||
| public static class Builder { | ||
| Integer clientIndex; | ||
| Integer subsetSize = 10; | ||
|
|
||
| Boolean sortAddresses; | ||
| PolicySelection childPolicy; | ||
|
|
||
| public Builder setClientIndex(Integer clientIndex) { | ||
| checkState(clientIndex != null); | ||
| // Indices must be positive integers. | ||
| checkState(clientIndex >= 0); | ||
| this.clientIndex = clientIndex; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setSubsetSize(Integer subsetSize) { | ||
| checkArgument(subsetSize != null); | ||
| // subsetSize of 1 is equivalent to `pick_first`. Use that policy if that behavior is | ||
| // desired. | ||
| // Fallback to default of 10 of condition is not satisfied. | ||
| checkArgument(subsetSize > 1); | ||
| this.subsetSize = subsetSize; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setSortAddresses(Boolean sortAddresses) { | ||
| checkArgument(sortAddresses != null); | ||
| this.sortAddresses = sortAddresses; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setChildPolicy(PolicySelection childPolicy) { | ||
| checkState(childPolicy != null); | ||
| this.childPolicy = childPolicy; | ||
| return this; | ||
| } | ||
|
|
||
| public DeterministicSubsettingLoadBalancerConfig build() { | ||
| checkState(childPolicy != null); | ||
| checkState(clientIndex != null); | ||
| return new DeterministicSubsettingLoadBalancerConfig( | ||
| clientIndex, subsetSize, sortAddresses, childPolicy); | ||
| } | ||
| } | ||
| } | ||
| } | ||
108 changes: 108 additions & 0 deletions
108
util/src/main/java/io/grpc/util/DeterministicSubsettingLoadBalancerProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| /* | ||
| * Copyright 2023 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.util; | ||
|
|
||
| import io.grpc.Internal; | ||
| import io.grpc.LoadBalancer; | ||
| import io.grpc.LoadBalancerProvider; | ||
| import io.grpc.LoadBalancerRegistry; | ||
| import io.grpc.NameResolver.ConfigOrError; | ||
| import io.grpc.Status; | ||
| import io.grpc.internal.JsonUtil; | ||
| import io.grpc.internal.ServiceConfigUtil; | ||
| import io.grpc.internal.ServiceConfigUtil.PolicySelection; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| @Internal | ||
| public final class DeterministicSubsettingLoadBalancerProvider extends LoadBalancerProvider { | ||
|
|
||
| @Override | ||
| public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) { | ||
| return new DeterministicSubsettingLoadBalancer(helper); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isAvailable() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public int getPriority() { | ||
| return 5; | ||
| } | ||
|
|
||
| @Override | ||
| public String getPolicyName() { | ||
| return "deterministic_subsetting"; | ||
| } | ||
|
|
||
| @Override | ||
| public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> rawConfig) { | ||
| try { | ||
| return parseLoadBalancingPolicyConfigInternal(rawConfig); | ||
| } catch (RuntimeException e) { | ||
| return ConfigOrError.fromError( | ||
| Status.UNAVAILABLE | ||
| .withCause(e) | ||
| .withDescription("Failed parsing configuration for " + getPolicyName())); | ||
| } | ||
| } | ||
|
|
||
| private ConfigOrError parseLoadBalancingPolicyConfigInternal(Map<String, ?> rawConfig) { | ||
| Integer clientIndex = JsonUtil.getNumberAsInteger(rawConfig, "clientIndex"); | ||
| Integer subsetSize = JsonUtil.getNumberAsInteger(rawConfig, "subsetSize"); | ||
| Boolean sortAddresses = JsonUtil.getBoolean(rawConfig, "sortAddresses"); | ||
|
|
||
| List<ServiceConfigUtil.LbConfig> childConfigCandidates = | ||
| ServiceConfigUtil.unwrapLoadBalancingConfigList( | ||
| JsonUtil.getListOfObjects(rawConfig, "childPolicy")); | ||
| if (childConfigCandidates == null || childConfigCandidates.isEmpty()) { | ||
| return ConfigOrError.fromError( | ||
| Status.INTERNAL.withDescription( | ||
| "No child policy in deterministic_subsetting LB policy " + rawConfig)); | ||
| } | ||
|
|
||
| ConfigOrError selectedConfig = | ||
| ServiceConfigUtil.selectLbPolicyFromList( | ||
| childConfigCandidates, LoadBalancerRegistry.getDefaultRegistry()); | ||
|
|
||
| DeterministicSubsettingLoadBalancer.DeterministicSubsettingLoadBalancerConfig.Builder | ||
| configBuilder = | ||
| new DeterministicSubsettingLoadBalancer.DeterministicSubsettingLoadBalancerConfig | ||
| .Builder(); | ||
|
|
||
| configBuilder.setChildPolicy((PolicySelection) selectedConfig.getConfig()); | ||
|
|
||
| if (clientIndex != null) { | ||
| configBuilder.setClientIndex(clientIndex); | ||
| } else { | ||
| return ConfigOrError.fromError( | ||
| Status.INTERNAL.withDescription( | ||
| "No client index set, cannot determine subsets " + rawConfig)); | ||
| } | ||
|
|
||
| if (subsetSize != null) { | ||
| configBuilder.setSubsetSize(subsetSize); | ||
| } | ||
|
|
||
| if (sortAddresses != null) { | ||
| configBuilder.setSortAddresses(sortAddresses); | ||
| } | ||
| return ConfigOrError.fromConfig(configBuilder.build()); | ||
| } | ||
| } |
1 change: 1 addition & 0 deletions
1
util/src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider | ||
| io.grpc.util.OutlierDetectionLoadBalancerProvider | ||
| io.grpc.util.DeterministicSubsettingLoadBalancerProvider |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.