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:
Jon Travis
2012-05-28 21:01:55 -07:00
committed by Dave Syer
parent 9eebfe2288
commit 759d40ed5d
3 changed files with 187 additions and 11 deletions

View File

@@ -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() {

View File

@@ -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)));
}
}
}

View File

@@ -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();
}
}
}