Added RetrySimulator - simulate retry + backoff loops

- The RetrySimulator can be used to calibrate
    retry + backoff tuples.
This commit is contained in:
Jon Travis
2012-05-28 21:00:52 -07:00
committed by Dave Syer
parent 048e3dec35
commit 9eebfe2288
7 changed files with 397 additions and 3 deletions

View File

@@ -39,7 +39,7 @@ import org.springframework.util.ClassUtils;
* @author Dave Syer
* @author Gary Russell
*/
public class ExponentialBackOffPolicy implements BackOffPolicy {
public class ExponentialBackOffPolicy implements SleepingBackOffPolicy<ExponentialBackOffPolicy> {
protected final Log logger = LogFactory.getLog(this.getClass());
/**
@@ -84,6 +84,24 @@ public class ExponentialBackOffPolicy implements BackOffPolicy {
this.sleeper = sleeper;
}
public ExponentialBackOffPolicy withSleeper(Sleeper sleeper) {
ExponentialBackOffPolicy res = newInstance();
cloneValues(res);
res.setSleeper(sleeper);
return res;
}
protected ExponentialBackOffPolicy newInstance() {
return new ExponentialBackOffPolicy();
}
protected void cloneValues(ExponentialBackOffPolicy target) {
target.setInitialInterval(getInitialInterval());
target.setMaxInterval(getMaxInterval());
target.setMultiplier(getMultiplier());
target.setSleeper(sleeper);
}
/**
* Set the initial sleep interval value. Default is <code>100</code>
* millisecond. Cannot be set to a value less than one.

View File

@@ -27,7 +27,7 @@ package org.springframework.retry.backoff;
* @author Rob Harrop
* @author Dave Syer
*/
public class FixedBackOffPolicy extends StatelessBackOffPolicy {
public class FixedBackOffPolicy extends StatelessBackOffPolicy implements SleepingBackOffPolicy<FixedBackOffPolicy> {
/**
* Default back off period - 1000ms.
@@ -41,7 +41,14 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy {
private Sleeper sleeper = new ObjectWaitSleeper();
public FixedBackOffPolicy withSleeper(Sleeper sleeper) {
FixedBackOffPolicy res = new FixedBackOffPolicy();
res.setBackOffPeriod(backOffPeriod);
res.setSleeper(sleeper);
return res;
}
/**
* Public setter for the {@link Sleeper} strategy.
* @param sleeper the sleeper to set defaults to {@link ObjectWaitSleeper}.
@@ -78,4 +85,8 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy {
throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
}
}
public String toString() {
return "FixedBackOffPolicy[backOffPeriod=" + backOffPeriod + "]";
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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;
/**
* A interface which can be mixed in by {@link BackOffPolicy}s indicating that they sleep
* when backing off.
*/
public interface SleepingBackOffPolicy<T extends SleepingBackOffPolicy> extends BackOffPolicy {
/**
* Clone the policy and return a new policy which uses the passed sleeper.
*
* @param sleeper Target to be invoked any time the backoff policy sleeps
* @return a clone of this policy which will have all of its backoff sleeps
* routed into the passed sleeper
*/
T withSleeper(Sleeper sleeper);
}

View File

@@ -23,6 +23,7 @@ import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.context.RetryContextSupport;
import org.springframework.util.ClassUtils;
/**
*
@@ -150,4 +151,8 @@ public class SimpleRetryPolicy implements RetryPolicy {
private boolean retryForException(Throwable ex) {
return retryableClassifier.classify(ex);
}
public String toString() {
return ClassUtils.getShortName(getClass()) + "[maxAttempts=" + maxAttempts + "]";
}
}

View File

@@ -0,0 +1,114 @@
/*
* 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.support;
import java.util.*;
/**
* The results of a simulation.
*/
public class RetrySimulation {
private final List<SleepSequence> sleepSequences = new ArrayList<SleepSequence>();
private final Map<Long, Long> sleepHistogram = new HashMap<Long, Long>();
/**
* Add a sequence of sleeps to the simulation.
*/
public void addSequence(List<Long> sleeps) {
for (Long sleep : sleeps) {
Long existingHisto = sleepHistogram.get(sleep);
if (existingHisto == null) {
sleepHistogram.put(sleep, 1l);
} else {
sleepHistogram.put(sleep, existingHisto + 1);
}
}
sleepSequences.add(new SleepSequence(sleeps));
}
/**
* @return Returns a list of all the unique sleep values which were executed within
* all simulations.
*/
public List<Long> getUniqueSleeps() {
List<Long> res = new ArrayList<Long>(sleepHistogram.keySet());
Collections.sort(res);
return res;
}
/**
* @return the count of each sleep which was seen throughout all sleeps.
* histogram[i] = sum(getUniqueSleeps()[i])
*/
public List<Long> getUniqueSleepsHistogram() {
List<Long> res = new ArrayList<Long>(sleepHistogram.size());
for (Long sleep : getUniqueSleeps()) {
res.add(sleepHistogram.get(sleep));
}
return res;
}
/**
* @return the longest total time slept by a retry sequence.
*/
public SleepSequence getLongestTotalSleepSequence() {
SleepSequence longest = null;
for (SleepSequence sequence : sleepSequences) {
if (longest == null || sequence.getTotalSleep() > longest.getTotalSleep()) {
longest = sequence;
}
}
return longest;
}
public static class SleepSequence {
private final List<Long> sleeps;
private final long longestSleep;
private final long totalSleep;
public SleepSequence(List<Long> sleeps) {
this.sleeps = sleeps;
this.longestSleep = Collections.max(sleeps);
long totalSleep = 0;
for (Long sleep : sleeps) {
totalSleep += sleep;
}
this.totalSleep = totalSleep;
}
public List<Long> getSleeps() {
return sleeps;
}
/**
* Returns the longest individual sleep within this sequence.
*/
public long getLongestSleep() {
return longestSleep;
}
public long getTotalSleep() {
return totalSleep;
}
public String toString() {
return "totalSleep=" + totalSleep + ": " + sleeps.toString();
}
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.support;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.backoff.Sleeper;
import org.springframework.retry.backoff.SleepingBackOffPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link RetrySimulator} is a tool for exercising retry + backoff operations.
*
* When calibrating a set of retry + backoff pairs, it is useful to know the behaviour
* of the retry for various scenarios.
*
* Things you may want to know:
* - Does a 'maxInterval' of 5000 ms in my backoff even matter?
* (This is often the case when retry counts are low -- so why set the max interval
* at something that cannot be achieved?)
* - What are the typical sleep durations for threads in a retry
* - What was the longest sleep duration for any retry sequence
*
* The simulator provides this information by executing a retry + backoff pair until failure
* (that is all retries are exhausted). The information about each retry is provided
* as part of the {@link RetrySimulation}.
*
* Note that the impetus for this class was to expose the timings which are possible with
* {@link org.springframework.retry.backoff.ExponentialRandomBackOffPolicy}, which provides
* random values and must be looked at over a series of trials.
*
* @author Jon Travis
*/
public class RetrySimulator {
private final SleepingBackOffPolicy backOffPolicy;
private final RetryPolicy retryPolicy;
public RetrySimulator(SleepingBackOffPolicy backOffPolicy, RetryPolicy retryPolicy) {
this.backOffPolicy = backOffPolicy;
this.retryPolicy = retryPolicy;
}
/**
* Execute the simulator for a give # of iterations.
*
* @param numSimulations Number of simulations to run
* @return the outcome of all simulations
*/
public RetrySimulation executeSimulation(int numSimulations) {
RetrySimulation simulation = new RetrySimulation();
for (int i=0; i<numSimulations; i++) {
simulation.addSequence(executeSingleSimulation());
}
return simulation;
}
/**
* Execute a single simulation
* @return The sleeps which occurred within the single simulation.
*/
public List<Long> executeSingleSimulation() {
StealingSleeper stealingSleeper = new StealingSleeper();
SleepingBackOffPolicy stealingBackoff = backOffPolicy.withSleeper(stealingSleeper);
RetryTemplate template = new RetryTemplate();
template.setBackOffPolicy(stealingBackoff);
template.setRetryPolicy(retryPolicy);
try {
template.execute(new FailingRetryCallback<Object>());
} catch(FailingRetryException e) {
} catch(Exception e) {
throw new RuntimeException("Unexpected exception", e);
}
return stealingSleeper.getSleeps();
}
static class FailingRetryCallback<Object> implements RetryCallback<Object> {
public Object doWithRetry(RetryContext context) throws Exception {
throw new FailingRetryException();
}
}
static class FailingRetryException extends Exception {
}
static class StealingSleeper implements Sleeper {
private final List<Long> sleeps = new ArrayList<Long>();
public void sleep(long backOffPeriod) throws InterruptedException {
sleeps.add(backOffPeriod);
}
public List<Long> getSleeps() {
return sleeps;
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.support;
import org.junit.Test;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class RetrySimulationTests {
@Test
public void testSimulatorExercisesFixedBackoff() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(400);
RetrySimulator simulator = new RetrySimulator(backOffPolicy, retryPolicy);
RetrySimulation simulation = simulator.executeSimulation(1000);
System.out.println(backOffPolicy);
System.out.println("Longest sequence " + simulation.getLongestTotalSleepSequence());
System.out.println("All Sleeps: " + simulation.getUniqueSleeps());
System.out.println("Sleep Occurences: " + simulation.getUniqueSleepsHistogram());
assertEquals(asList(400l, 400l, 400l, 400l), simulation.getLongestTotalSleepSequence().getSleeps());
assertEquals(asList(400l), simulation.getUniqueSleeps());
assertEquals(asList(4000l), simulation.getUniqueSleepsHistogram());
}
@Test
public void testSimulatorExercisesExponentialBackoff() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(30000);
backOffPolicy.setInitialInterval(100);
RetrySimulator simulator = new RetrySimulator(backOffPolicy, retryPolicy);
RetrySimulation simulation = simulator.executeSimulation(1000);
System.out.println(backOffPolicy);
System.out.println("Longest sequence " + simulation.getLongestTotalSleepSequence());
System.out.println("All Sleeps: " + simulation.getUniqueSleeps());
System.out.println("Sleep Occurences: " + simulation.getUniqueSleepsHistogram());
assertEquals(asList(100l, 200l, 400l, 800l), simulation.getLongestTotalSleepSequence().getSleeps());
assertEquals(asList(100l, 200l, 400l, 800l), simulation.getUniqueSleeps());
assertEquals(asList(1000l, 1000l, 1000l, 1000l), simulation.getUniqueSleepsHistogram());
}
@Test
public void testSimulatorExercisesRandomExponentialBackoff() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
ExponentialBackOffPolicy backOffPolicy = new ExponentialRandomBackOffPolicy();
backOffPolicy.setMultiplier(2);
backOffPolicy.setMaxInterval(30000);
backOffPolicy.setInitialInterval(100);
RetrySimulator simulator = new RetrySimulator(backOffPolicy, retryPolicy);
RetrySimulation simulation = simulator.executeSimulation(10000);
System.out.println(backOffPolicy);
System.out.println("Longest sequence " + simulation.getLongestTotalSleepSequence());
System.out.println("All Sleeps: " + simulation.getUniqueSleeps());
System.out.println("Sleep Occurences: " + simulation.getUniqueSleepsHistogram());
assertEquals(asList(100l, 200l, 400l, 800l), simulation.getLongestTotalSleepSequence().getSleeps());
assertEquals(asList(100l, 200l, 300l, 400l, 500l, 600l, 700l, 800l), simulation.getUniqueSleeps());
for (long histo : simulation.getUniqueSleepsHistogram()) {
assertTrue("Should have experienced a sleep more than " + histo + " times",
histo > 1000);
}
}
}