-
Notifications
You must be signed in to change notification settings - Fork 935
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Motivation: A load-balancing strategy such as round robin can be used in `EndpointSelector` and elsewhere. For example, in the event loop scheduler, requests can be distributed using round robin to determine which event loop to use. This PR is preliminary work to resolve #5289 and #5537 Modifications: - `LoadBalancer<T, C>` is the root interface all load balancers should implement. - `T` is the type of a candidate selected by strategies. - `C` is the type of context that is used when selecting a candidate. - `UpdatableLoadBalancer<T, C>` is a stateful load balancer to which new endpoints are updated. `RampingUpLoadBalancer` is the only implementation for `UpdatableLoadBalancer`. Other load balances will be re-created when new endpoints are added because they can always be reconstructed for the same results. - `Weighted` is a new API that represents the weight of an object. - If an object is `Weighted`, a weight function is not necessary when creating weighted-based load balancers. - `Endpoint` now implements `Weighted`. - `EndpointSelectionStategy` uses `DefaultEndpointSelector` to create a `LoadBalancer<Endpoint, ClientRequestContext>` internally and delegates the selection logic to it. - Each `EndpointSelectionStategy` implements `LoadBalancerFactory` to update the existing `LoadBalancer` or create a new `LoadBalancer` when endpoints are updated. - The following implementations are migrated from `**Strategy`. Except for `RampingUpLoadBalancer` which has some minor changes, most of the logic was ported as is. - `RampingUpLoadBalancer` - `Weight` prefix is dropped for simplicity. There may be no problem conveying the behavior. - Refactored to use a lock to guarantee thread-safety and sequential access. - A `RampingUpLoadBalancer` is now created from a list of candidates. If an executor is used to build the initial state, null is returned right after it is created. - `AbstractRampingUpLoadBalancerBuilder` is added to share common code for `RampingUpLoadBalancerBuilder` and `WeightRampingUpStrategyBuilder` - Fixed xDS implementations to use the new API when implementing load balancing strategies. - Deprecation) `EndpointWeightTransition` in favor of `WeightTransition` Result: - You can now create `LoadBalancer` using various load balancing strategies to select an element from a list of candidates. ```java List<Endpoint> candidates = ...; LoadBalancer.ofRoundRobin(candidates); LoadBalancer.ofWeightedRoundRobin(candidates); LoadBalancer.ofSticky(candidates, contextHasher); LoadBalancer.ofWeightedRandom(candidates); LoadBalancer.ofRampingUp(candidates); ```
- Loading branch information
Showing
40 changed files
with
2,705 additions
and
1,100 deletions.
There are no files selected for viewing
This file contains 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
90 changes: 90 additions & 0 deletions
90
core/src/main/java/com/linecorp/armeria/client/endpoint/DefaultEndpointSelector.java
This file contains 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,90 @@ | ||
/* | ||
* Copyright 2024 LINE Corporation | ||
* | ||
* LINE Corporation licenses this file to you 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: | ||
* | ||
* https://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 com.linecorp.armeria.client.endpoint; | ||
|
||
import java.util.List; | ||
|
||
import com.linecorp.armeria.client.ClientRequestContext; | ||
import com.linecorp.armeria.client.Endpoint; | ||
import com.linecorp.armeria.common.annotation.Nullable; | ||
import com.linecorp.armeria.common.loadbalancer.LoadBalancer; | ||
import com.linecorp.armeria.common.util.ListenableAsyncCloseable; | ||
import com.linecorp.armeria.internal.common.util.ReentrantShortLock; | ||
|
||
final class DefaultEndpointSelector<T extends LoadBalancer<Endpoint, ClientRequestContext>> | ||
extends AbstractEndpointSelector { | ||
|
||
private final LoadBalancerFactory<T> loadBalancerFactory; | ||
@Nullable | ||
private volatile T loadBalancer; | ||
private boolean closed; | ||
private final ReentrantShortLock lock = new ReentrantShortLock(); | ||
|
||
DefaultEndpointSelector(EndpointGroup endpointGroup, | ||
LoadBalancerFactory<T> loadBalancerFactory) { | ||
super(endpointGroup); | ||
this.loadBalancerFactory = loadBalancerFactory; | ||
if (endpointGroup instanceof ListenableAsyncCloseable) { | ||
((ListenableAsyncCloseable) endpointGroup).whenClosed().thenAccept(unused -> { | ||
lock.lock(); | ||
try { | ||
closed = true; | ||
final T loadBalancer = this.loadBalancer; | ||
if (loadBalancer != null) { | ||
loadBalancer.close(); | ||
} | ||
} finally { | ||
lock.unlock(); | ||
} | ||
}); | ||
} | ||
initialize(); | ||
} | ||
|
||
@Override | ||
protected void updateNewEndpoints(List<Endpoint> endpoints) { | ||
lock.lock(); | ||
try { | ||
if (closed) { | ||
return; | ||
} | ||
loadBalancer = loadBalancerFactory.newLoadBalancer(loadBalancer, endpoints); | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public Endpoint selectNow(ClientRequestContext ctx) { | ||
final T loadBalancer = this.loadBalancer; | ||
if (loadBalancer == null) { | ||
return null; | ||
} | ||
return loadBalancer.pick(ctx); | ||
} | ||
|
||
@FunctionalInterface | ||
interface LoadBalancerFactory<T> { | ||
T newLoadBalancer(@Nullable T oldLoadBalancer, List<Endpoint> candidates); | ||
|
||
@SuppressWarnings("unchecked") | ||
default T unsafeCast(LoadBalancer<Endpoint, ?> loadBalancer) { | ||
return (T) loadBalancer; | ||
} | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Oops, something went wrong.