-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RoundRobinLoadBalancer
: re-subscribe when all hosts become unhealthy
Motivation: It’s possible for the RRLB to turn into a state when all available hosts become unhealthy. Possible scenarios: - DNS returned incorrect result; - All hosts were restarted and got different addresses. RRL will be in a dead state until TTL expires, which may take some time. Modifications: - When RRLB detects that all hosts are unhealthy, it re-subscribes to the SD events publisher to trigger a new resolution; - Add `RoundRobinLoadBalancerFactory.Builder#healthCheckResubscribeInterval` option to configure new feature; - Wrap `backgroundExecutor` with `NormalizedTimeSourceExecutor` to make sure `currentTime` is always positive; - Test behavior when the new features is enabled/disabled; - Add `DurationUtils.ensureNonNegative(...)` utility for validation; - Make `TestExecutor(long)` constructor public to test `NormalizedTimeSourceExecutor`; - Add `SequentialPublisherSubscriberFunction.numberOfSubscribers()` to verify how many subscribers the `TestPublisher` already saw; Result: RRLB forces SD to re-resolve addresses by cancelling the current subscription and re-subscribing to the publisher when it detects that all hosts become unhealthy.
- Loading branch information
1 parent
02ebab9
commit f7fc25f
Showing
10 changed files
with
628 additions
and
211 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
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
41 changes: 41 additions & 0 deletions
41
...-loadbalancer/src/main/java/io/servicetalk/loadbalancer/NormalizedTimeSourceExecutor.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,41 @@ | ||
/* | ||
* Copyright © 2023 Apple Inc. and the ServiceTalk project 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.servicetalk.loadbalancer; | ||
|
||
import io.servicetalk.concurrent.api.DelegatingExecutor; | ||
import io.servicetalk.concurrent.api.Executor; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
import static java.util.concurrent.TimeUnit.NANOSECONDS; | ||
|
||
/** | ||
* An {@link Executor} that always starts counting {@link #currentTime(TimeUnit)} from {@code 0}. | ||
*/ | ||
final class NormalizedTimeSourceExecutor extends DelegatingExecutor { | ||
|
||
private final long offset; | ||
|
||
NormalizedTimeSourceExecutor(final Executor delegate) { | ||
super(delegate); | ||
offset = delegate.currentTime(NANOSECONDS); | ||
} | ||
|
||
@Override | ||
public long currentTime(final TimeUnit unit) { | ||
return delegate().currentTime(unit) - offset; | ||
} | ||
} |
454 changes: 291 additions & 163 deletions
454
...cetalk-loadbalancer/src/main/java/io/servicetalk/loadbalancer/RoundRobinLoadBalancer.java
Large diffs are not rendered by default.
Oops, something went wrong.
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
62 changes: 62 additions & 0 deletions
62
...dbalancer/src/test/java/io/servicetalk/loadbalancer/NormalizedTimeSourceExecutorTest.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,62 @@ | ||
/* | ||
* Copyright © 2023 Apple Inc. and the ServiceTalk project 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.servicetalk.loadbalancer; | ||
|
||
import io.servicetalk.concurrent.api.Executor; | ||
import io.servicetalk.concurrent.api.TestExecutor; | ||
|
||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.ValueSource; | ||
|
||
import static java.lang.Long.MAX_VALUE; | ||
import static java.lang.Long.MIN_VALUE; | ||
import static java.util.concurrent.TimeUnit.NANOSECONDS; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
|
||
@SuppressWarnings("NumericOverflow") | ||
class NormalizedTimeSourceExecutorTest { | ||
|
||
private TestExecutor testExecutor; | ||
private Executor executor; | ||
|
||
@AfterEach | ||
void tearDown() throws Exception { | ||
executor.closeAsync().toFuture().get(); | ||
} | ||
|
||
void setUp(long initialValue) { | ||
testExecutor = new TestExecutor(initialValue); | ||
executor = new NormalizedTimeSourceExecutor(testExecutor); | ||
assertThat("Unexpected initial value", executor.currentTime(NANOSECONDS), is(0L)); | ||
} | ||
|
||
void advanceAndVerify(long advanceByNanos, long expected) { | ||
testExecutor.advanceTimeByNoExecuteTasks(advanceByNanos, NANOSECONDS); | ||
assertThat(executor.currentTime(NANOSECONDS), is(expected)); | ||
} | ||
|
||
@ParameterizedTest(name = "{displayName} [{index}]: initialValue={0}") | ||
@ValueSource(longs = {MIN_VALUE, -100, 0, 100, MAX_VALUE}) | ||
void minValue(long initialValue) { | ||
setUp(initialValue); | ||
advanceAndVerify(10, 10); | ||
advanceAndVerify(MAX_VALUE - 10, MAX_VALUE); | ||
advanceAndVerify(10, MAX_VALUE + 10); | ||
advanceAndVerify(MAX_VALUE - 8, 0); | ||
} | ||
} |
Oops, something went wrong.