Added RetrySimulator - simulate retry + backoff loops
- The RetrySimulator can be used to calibrate
retry + backoff tuples.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user