RESOLVED - issue BATCH-425: Add documentation for RepeatTemplate

OPEN - issue BATCH-424: Reference Documentation for RetryTemplate
This commit is contained in:
dsyer
2008-03-13 09:38:19 +00:00
parent c14e334072
commit 8b0b12c70e
4 changed files with 331 additions and 11 deletions

View File

@@ -5,7 +5,7 @@
<title>Repeat</title>
<section>
<title>Repeat Template</title>
<title>RepeatTemplate</title>
<para>Batch processing is about repetitive actions - either as a simple
optimisation, or as part of a job. To strategise and generalise the
@@ -192,7 +192,7 @@ template.iterate(new RepeatCallback() {
<classname>RepeatCallback</classname>, the
<classname>RepeatTemplate</classname> consults an
<classname>ExceptionHandler</classname> which can decide whether or not to
re-throw the exception. </para>
re-throw the exception.</para>
<para><programlisting>public interface ExceptionHandler {
@@ -247,7 +247,7 @@ template.iterate(new RepeatCallback() {
<methodname>close</methodname> callbacks come before and after the entire
iteration, and <methodname>before</methodname>,
<methodname>after</methodname> and <methodname>onError</methodname> apply
to the individual RepeatCallback calls. </para>
to the individual RepeatCallback calls.</para>
<para>Note that when there is more than one listener, they are in a list,
so there is an order. In this case <methodname>open</methodname> and
@@ -270,4 +270,53 @@ template.iterate(new RepeatCallback() {
iteration in the same thread (the same as a normal
<classname>RepeatTemplate</classname>).</para>
</section>
<section>
<title>Declarative Iteration</title>
<para>Sometimes there is some business processing that you know you want
to repeat every time it happens. The classic example of this is the
optimization of a message pipeline - it is more efficient to process a
batch of messages, if they are arriving frequently, than to bear the cost
of a separate transaction for every message. Spring Batch provides an AOP
interceptor that wraps a method call in a
<classname>RepeatOperations</classname> for just this purpose. The
<classname>RepeatOperationsInterceptor</classname> executes the
intercepted method and repeats according to the
<classname>CompetionPolicy</classname> in the provided
<classname>RepeatTemplate</classname>.</para>
<para>Here is an example of declarative iteration using the Spring AOP
namespace to repeat a service call to a method called
<methodname>processMessage</methodname> (for more detail on how to
configure AOP interceptors see the Spring User Guide):</para>
<programlisting>&lt;aop:config&gt;
&lt;aop:pointcut id="transactional"
expression="execution(* com...*Service.processMessage(..))" /&gt;
&lt;aop:advisor pointcut-ref="transactional"
advice-ref="retryAdvice" order="-1"/&gt;
&lt;/aop:config&gt;
&lt;bean id="retryAdvice"
class="org.springframework.batch.repeat.interceptor.RepeatOperationsInterceptor"/&gt;
</programlisting>
<para>The example above uses a default
<classname>RepeatTemplate</classname> inside the interceptor. To change
the policies, listeners etc. you only need to inject an instance of
<classname>RepeatTemplate</classname> into the interceptor.</para>
<para>If the intercepted method returns <code>void</code> then the
interceptor always returns ExitStatus.CONTINUABLE (so there is a danger of
an infinite loop if the <classname>CompletionPolicy</classname> does not
have a finite end point). Otherwise it returns
<code>ExitStatus.CONTINUABLE</code> until the return value from the
intercepted method is null, at which point it returns
<code>ExitStatus.FINISHED</code>. So the business logic inside the target
method can signal that there is no more work to do by returning
<code>null</code>, or by throwing an exception that is re-thrown by the
<classname>ExceptionHandler</classname> in the provided
<classname>RepeatTemplate</classname>.</para>
</section>
</chapter>

View File

@@ -1,12 +1,284 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter>
<title>Retry</title>
<section>
<title></title>
<title>RetryTemplate</title>
<para></para>
<para>To make processing more robust and less prone to failure, sometimes
it helps to automatically retry a failed operation in case it might
succeed on a subsequent attempt. Errors that are susceptible to this kind
of treatment are transient in nature, for example a remote call to a web
service or RMI service that fails because of a network glitch, or a
<classname>DeadLockLoserException</classname> in a database update. To
automate the retry of such operations Spring Batch has the
<classname>RetryOperations</classname> strategy. The
<classname>RetryOperations</classname> interface looks like this:</para>
<para><programlisting>public interface RetryOperations {
Object execute(RetryCallback retryCallback) throws Exception;
}</programlisting>where the callback is a simple interface that allows you to
insert some business logic to be retried</para>
<para><programlisting>public interface RetryCallback {
Object doWithRetry(RetryContext context) throws Throwable;
}</programlisting>The callback is executed and if it fails (by throwing an
<classname>Exception</classname>), it will be retried until either it is
successful, or the implementation decides to abort.</para>
<para>The simplest general purpose implementation of
<classname>RetryOperations</classname> is
<classname>RetryTemplate</classname>. It could be used like this</para>
<programlisting>RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(new TimeoutRetryPolicy(30000L));
Object result = template.execute(new RetryCallback() {
public Object doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});</programlisting>
<para>In the example we execute a web service call and return the result
to the user. If that call fails then it is retried until a timeout is
reached.</para>
<section>
<title>RetryContext</title>
<para>The method parameter for the <classname>RetryCallback</classname>
is a <classname>RetryContext</classname>. Many callbacks will simply
ignore the context, but if necessary it can be used as an attribute bag
to store data for the duration of the iteration.</para>
<para>A <classname>RetryContext</classname> will have a parent context
if there is a nested retry in progress in the same thread. The parent
context is occasionally useful for storing data that need to be shared
between calls to <methodname>execute</methodname>.</para>
</section>
</section>
<section>
<title>Retry Policies</title>
<para>Inside a <classname>RetryTemplate</classname> the decision to retry
or fail in the <methodname>execute</methodname> method is determined by a
<classname>RetryPolicy</classname> which is also a factory for the
<classname>RetryContext</classname>. The
<classname>RetryTemplate</classname> has the reponsibility to use the
current policy to create a <classname>RetryContext</classname> and pass
that in to the <classname>RetryCallback</classname> at every attempt.
After a callback fails the <classname>RetryTemplate</classname> has to
make a call to the <classname>RetryPolicy</classname> to ask it to update
its state (which will be stored in the
<classname>RetryContext</classname>), then it asks the policy if another
attempt can be made. If another attempt cannot be made (e.g. a limit is
reached or a timeout is detected) then the policy is also responsible for
handling the exhausted state. Simple implementations will just throw
<classname>RetryExhaustedException</classname>, and any enclosing
transaction will be rolled back. More sophisticated implementations might
attempt to take some recovery action, in which case the transaction can
remain intact.</para>
<section>
<title>Stateless Retry</title>
<para>In the simplest case a retry is just a while loop - the
<classname>RetryTemplate</classname> can just keep trying until it
either succeeds or fails. The <classname>RetryContext</classname> is
storing some state to determine whether to retry or abort, but this
state is on the stack and there is no need to store it anywhere
globally, so we call this stateless retry. The distinction between
stateless and stateful retry is contained in the implementation of the
<classname>RetryPolicy</classname> (the
<classname>RetryTemplate</classname> can handle both).</para>
<para>Spring Batch provides some simple general purpose implementations
of stateless <classname>RetryPolicy</classname>, for example a
<classname>SimpleRetryPolicy</classname>, and the
<classname>TimeoutRetryPolicy</classname> used in the example above. The
<classname>SimpleRetryPolicy</classname> just allows a retry on any of a
named list of exception types, up to a fixed number of times.</para>
<para>Users might need to implement their own retry policies for more
customized decisions, e.g. if there is a well-known solution-specific
classification of exceptions into retryable and not retryable.</para>
</section>
<section>
<title>Stateful Retry</title>
<para>Where the failure has caused a transactional resource to become
invalid there are some special considerations. This does not apply to a
simple remote call because there was no transactional resource
(usually), but it does sometimes apply to a database update, especially
when using Hibernate. In this case it only makes sense to rethrow the
exception that called the failure immediately, so that the transaction
can roll back, and we can start a new valid one.</para>
<para>In these cases a stateless retry is not good enough because the
re-throw and roll back necessarily involve leaving the
<code>RetryOperations.execute()</code> method and potentially losing the
context that was on the stack. To avoid losing it we have to introduce a
storage strategy to lift it off the stack and put it (at a minimum) in
heap storage. For this purpose Spring Batch provides the
<classname>ItemReaderRetryPolicy</classname> and a storage strategy
<classname>RetryContextCache</classname>. The default implementation of
the <classname>RetryContextCache</classname> is in memory, using a
simple <classname>Map</classname>. Advanced usage with multiple
processes in a clustered environment might also consider implementing
the <classname>RetryContextCache</classname> with a cluster cache of
some sort (even in a clustered environment this might be
overkill).</para>
<section>
<title>Item processing and stateful retry</title>
<para>Part of the reponsibility of a stateful retry policy is to
recognise the failed operations when they come back in a new
transaction. To facilitate this in the commonest case where an object
of some type (like a message or message payload) is being processed,
Spring Batch provides the
<classname>ItemReaderRetryPolicy</classname>. This works in
conjunction with a special <classname>RetryCallback</classname>
implementation <classname>ItemReaderRetryCallback</classname>, which
in turn relies on the user providing an
<classname>ItemReader</classname> and an
<classname>ItemWriter</classname>. This callback implements the common
pattern where it reads from a reader and passes the result to a
writer.</para>
<para>The way the failed operations are recognised in this
implementation is by recognising the object that is returned from the
<classname>ItemReader</classname>. To recognise the item the user can
provide an <classname>ItemKeyGenerator</classname> strategy, either by
injecting it directly into the
<classname>ItemReaderRetryCallback</classname>, or by implementing the
interface in the <classname>ItemReader</classname>, or by accepting
the default which is to simply use the item to identify itself.</para>
<para>When the retry is exhausted, because a stateful retry is always
in a fresh transaction, there is also the option to handle the failed
item in a different way, instead of calling the
<classname>RetryCallback</classname> (which is presumed now to be
almost certain to fail). This option is provided by the
<classname>ItemRecoverer</classname> strategy. Like the key generator,
it can be directly injected or provided by implementing the interface
in either the <classname>ItemReader</classname> or
<classname>ItemWriter</classname>.</para>
<para>The decision to retry or not is actually delegated to a regular
stateless retry policy, so the usual concerns about limits and
timeouts can be injected into the
<classname>ItemReaderRetryPolicy</classname> through the delegate
property.</para>
</section>
</section>
</section>
<section>
<title>Backoff Policies</title>
<para>When retrying after a transient failure it often helps to wait a bit
before trying again, because usually the failure is caused by some problem
that will only be resolved by waiting. If a
<classname>RetryCallback</classname> fails, the
<classname>RetryTemplate</classname> can pause execution according to the
<classname>BackoffPolicy</classname> in place.</para>
<para><programlisting>public interface BackoffPolicy {
BackOffContext start(RetryContext context);
void backOff(BackOffContext backOffContext)
throws BackOffInterruptedException;
}</programlisting>A <classname>BackoffPolicy</classname> is free to implement
the backOff in any way it chooses. The policies provided by Spring Batch
out of the box all use <code>Object.wait()</code>. A common use case is to
backoff with an exponentially increasing wait period, to avoid two retries
getting into lock step and both failing - this is a lesson learned from
the ethernet. For this purpose Spring Batch provides the
<classname>ExponentialBackoffPolicy</classname>.</para>
</section>
<section>
<title>Listeners</title>
<para>Often it is useful to be able to receive additional callbacks for
cross cutting concerns across a number of different retries. For this
purpose Spring Batch provides the <classname>RetryListener</classname>
interface. The <classname>RetryTemplate</classname> allows users to
register <classname>RetryListener</classname>s, and they will be given
callbacks with the <classname>RetryContext</classname> and
<classname>Throwable</classname> where available during the
iteration.</para>
<para>The interface looks like this:</para>
<para><programlisting>public interface RetryListener {
void open(RetryContext context, RetryCallback callback);
void onError(RetryContext context, RetryCallback callback, Throwable e);
void close(RetryContext context, RetryCallback callback, Throwable e);
}
</programlisting>The <methodname>open</methodname> and
<methodname>close</methodname> callbacks come before and after the entire
retry in the simplest case, and <methodname>onError</methodname> applies
to the individual RetryCallback calls. The <methodname>close</methodname>
method might also receive a <classname>Throwable</classname>, if there has
been an error it is the last one thrown by the
<classname>RetryCallback</classname>.</para>
<para>Note that when there is more than one listener, they are in a list,
so there is an order. In this case <methodname>open</methodname> is called
in the same order, and <methodname>onError</methodname> and
<methodname>close</methodname> are called in reverse order.</para>
</section>
<section>
<title>Declarative Retry</title>
<para>Sometimes there is some business processing that you know you want
to retry every time it happens. The classic example of this is the remote
service call. Spring Batch provides an AOP interceptor that wraps a method
call in a <classname>RetryOperations</classname> for just this purpose.
The <classname>RetryOperationsInterceptor</classname> executes the
intercepted method and retries on failure according to the
<classname>RetryPolicy</classname> in the provided
<classname>RepeatTemplate</classname>. </para>
<para>Here is an example of declarative iteration using the Spring AOP
namespace to repeat a service call to a method called
<methodname>remoteCall</methodname> (for more detail on how to configure
AOP interceptors see the Spring User Guide):</para>
<programlisting>&lt;aop:config&gt;
&lt;aop:pointcut id="transactional"
expression="execution(* com...*Service.remoteCall(..))" /&gt;
&lt;aop:advisor pointcut-ref="transactional"
advice-ref="retryAdvice" order="-1"/&gt;
&lt;/aop:config&gt;
&lt;bean id="retryAdvice"
class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor"/&gt;
</programlisting>
<para>The example above uses a default
<classname>RetryTemplate</classname> inside the interceptor. To change the
policies, listeners etc. you only need to inject an instance of
<classname>RetryTemplate</classname> into the interceptor.</para>
</section>
</chapter>

View File

@@ -33,7 +33,6 @@ import org.springframework.batch.retry.RetryContext;
* the corresponding {@link BackOffContext} object created by the call to
* {@link #start}.
*
* @since 2.1
* @author Rob Harrop
* @author Dave Syer
*/

View File

@@ -19,15 +19,15 @@ template.setCompletionPolicy(new FixedChunkSizeCompletionPolicy(2));
template.iterate(new RepeatCallback() {
public boolean doInIteration(RepeatContext context) {
public ExitStatus doInIteration(RepeatContext context) {
// Do stuff in batch...
return true; // Return false to signal exhausted data
return ExitStatus.CONTINUABLE; // Return ExitStatus.FINISHED to signal exhausted data
}
});
+---
The callback is executed repeatedly, until the termination policy
The callback is executed repeatedly, until the completion policy
determines that the batch should end.
The framework provides <<<RetryOperations>>> for automatic retry of