Add the ExponentialRandomBackOffPolicy
- Gives better performance when run in a very
contentious environment. Specifically when
running multi-threaded tests where all threads
may start at the same time.
This commit is contained in:
@@ -184,7 +184,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExponentialBackOffContext implements BackOffContext {
|
||||
static class ExponentialBackOffContext implements BackOffContext {
|
||||
|
||||
private final double multiplier;
|
||||
|
||||
@@ -198,16 +198,32 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<Exponenti
|
||||
this.maxInterval = maxInterval;
|
||||
}
|
||||
|
||||
public synchronized long getSleepAndIncrement() {
|
||||
long sleep = this.interval;
|
||||
if (sleep > maxInterval) {
|
||||
sleep = (long) maxInterval;
|
||||
}
|
||||
else {
|
||||
this.interval *= this.multiplier;
|
||||
}
|
||||
return sleep;
|
||||
}
|
||||
public synchronized long getSleepAndIncrement() {
|
||||
long sleep = this.interval;
|
||||
if (sleep > maxInterval) {
|
||||
sleep = (long) maxInterval;
|
||||
}
|
||||
else {
|
||||
this.interval = getNextInterval();
|
||||
}
|
||||
return sleep;
|
||||
}
|
||||
|
||||
protected long getNextInterval() {
|
||||
return (long)(this.interval * this.multiplier);
|
||||
}
|
||||
|
||||
public double getMultiplier() {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
public long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public long getMaxInterval() {
|
||||
return maxInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2006-2012 the original author or 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 org.springframework.retry.backoff;
|
||||
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.retry.backoff.ExponentialBackOffPolicy} that
|
||||
* chooses a random multiple of the interval. The random multiple is selected based on
|
||||
* how many iterations have occurred.
|
||||
*
|
||||
* This has shown to at least be useful in testing scenarios where excessive contention is generated
|
||||
* by the test needing many retries. In test, usually threads are started at the same time, and thus
|
||||
* stomp together onto the next interval. Using this {@link BackOffPolicy} can help avoid that scenario.
|
||||
*
|
||||
* Example:
|
||||
* initialInterval = 50
|
||||
* multiplier = 2.0
|
||||
* maxInterval = 3000
|
||||
* numRetries = 5
|
||||
*
|
||||
* {@link ExponentialBackOffPolicy} yields: [50, 100, 200, 400, 800]
|
||||
*
|
||||
* {@link ExponentialRandomBackOffPolicy} may yield [50, 100, 100, 100, 600]
|
||||
* or [50, 100, 150, 400, 800]
|
||||
* @author Jon Travis
|
||||
*/
|
||||
public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
|
||||
/**
|
||||
* Returns a new instance of {@link org.springframework.retry.backoff.BackOffContext}, seeded with this
|
||||
* policies settings.
|
||||
*/
|
||||
public BackOffContext start(RetryContext context) {
|
||||
return new ExponentialRandomBackOffContext(getInitialInterval(), getMultiplier(), getMaxInterval());
|
||||
}
|
||||
|
||||
protected ExponentialBackOffPolicy newInstance() {
|
||||
return new ExponentialRandomBackOffPolicy();
|
||||
}
|
||||
|
||||
static class ExponentialRandomBackOffContext extends ExponentialBackOffPolicy.ExponentialBackOffContext {
|
||||
private final Random r = new Random();
|
||||
private final long initialInterval;
|
||||
private long intervalIdx;
|
||||
|
||||
public ExponentialRandomBackOffContext(long expSeed, double multiplier, long maxInterval) {
|
||||
super(expSeed, multiplier, maxInterval);
|
||||
this.initialInterval = expSeed;
|
||||
this.intervalIdx = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized long getNextInterval() {
|
||||
intervalIdx++;
|
||||
return initialInterval + initialInterval * Math.max(1, r.nextInt((int)Math.pow(getMultiplier(), intervalIdx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2006-2007 the original author or 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 org.springframework.retry.backoff;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetrySimulation;
|
||||
import org.springframework.retry.support.RetrySimulator;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ExponentialRandomBackOffPolicyTests extends TestCase {
|
||||
static final int NUM_TRIALS = 10000;
|
||||
static final int MAX_RETRIES = 6;
|
||||
|
||||
private ExponentialBackOffPolicy makeBackoffPolicy() {
|
||||
ExponentialBackOffPolicy policy = new ExponentialRandomBackOffPolicy();
|
||||
policy.setInitialInterval(50);
|
||||
policy.setMultiplier(2.0);
|
||||
policy.setMaxInterval(3000);
|
||||
return policy;
|
||||
}
|
||||
|
||||
private SimpleRetryPolicy makeRetryPolicy() {
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
|
||||
retryPolicy.setMaxAttempts(MAX_RETRIES);
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
public void testSingleBackoff() throws Exception {
|
||||
ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
|
||||
RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
|
||||
RetrySimulation simulation = simulator.executeSimulation(1);
|
||||
|
||||
List<Long> sleeps = simulation.getLongestTotalSleepSequence().getSleeps();
|
||||
System.out.println("Single trial of " + backOffPolicy + ": sleeps=" + sleeps);
|
||||
assertEquals(MAX_RETRIES - 1, sleeps.size());
|
||||
long initialInterval = backOffPolicy.getInitialInterval();
|
||||
for (int i=0; i<sleeps.size(); i++) {
|
||||
assertTrue(sleeps.get(i) % backOffPolicy.getInitialInterval() == 0);
|
||||
|
||||
long expectedMaxValue = (long) (initialInterval + initialInterval * Math.max(1, Math.pow(backOffPolicy.getMultiplier(), i)));
|
||||
assertTrue("Found a sleep [" + sleeps.get(i) + "] which exceeds our max expected value of " + expectedMaxValue + " at interval " + i,
|
||||
sleeps.get(i) < expectedMaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void testMultiBackOff() throws Exception {
|
||||
ExponentialBackOffPolicy backOffPolicy = makeBackoffPolicy();
|
||||
RetrySimulator simulator = new RetrySimulator(backOffPolicy, makeRetryPolicy());
|
||||
RetrySimulation simulation = simulator.executeSimulation(NUM_TRIALS);
|
||||
|
||||
System.out.println("Ran " + NUM_TRIALS + " backoff trials. Each trial retried " + MAX_RETRIES + " times");
|
||||
System.out.println("Policy: " + backOffPolicy);
|
||||
System.out.println("All generated backoffs:");
|
||||
System.out.println(" " + simulation.getUniqueSleeps());
|
||||
|
||||
System.out.println("Backoff frequencies:");
|
||||
System.out.print(" " + simulation.getUniqueSleepsHistogram());
|
||||
|
||||
long expectedSleep = backOffPolicy.getInitialInterval();
|
||||
List<Long> allSleeps = simulation.getUniqueSleeps();
|
||||
for (int j=0; j<allSleeps.size(); j++) {
|
||||
long sleep = allSleeps.get(j);
|
||||
assertEquals("Missing a multiple of " + backOffPolicy.getInitialInterval(), sleep, expectedSleep);
|
||||
|
||||
expectedSleep += backOffPolicy.getInitialInterval();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user