From 21f68a663af51223721e57f538b3f6e8fa7318d2 Mon Sep 17 00:00:00 2001 From: dsyer Date: Thu, 2 Oct 2008 18:44:51 +0000 Subject: [PATCH] Revise retry docs for 2.0m2 --- docs/src/site/docbook/reference/retry.xml | 329 +++++++++++----------- 1 file changed, 172 insertions(+), 157 deletions(-) diff --git a/docs/src/site/docbook/reference/retry.xml b/docs/src/site/docbook/reference/retry.xml index 5bc066325..413c6e041 100644 --- a/docs/src/site/docbook/reference/retry.xml +++ b/docs/src/site/docbook/reference/retry.xml @@ -17,37 +17,51 @@ RetryOperations strategy. The RetryOperations interface looks like this: - public interface RetryOperations { + T execute(RetryCallback retryCallback) throws Exception; -}where the callback is a simple interface that allows you to - insert some business logic to be retried + T execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback) + throws Exception; - public interface RetryCallback { + T execute(RetryCallback retryCallback, RetryState retryState) + throws Exception, ExhaustedRetryException; - Object doWithRetry(RetryContext context) throws Throwable; + T execute(RetryCallback retryCallback, RecoveryCallback recoveryCallback, + RetryState retryState) throws Exception; -}The callback is executed and if it fails (by throwing an +}]]>where the basic callback is a simple interface that + allows you to insert some business logic to be retried + + { + + T doWithRetry(RetryContext context) throws Throwable; + +}]]>The callback is executed and if it fails (by throwing an Exception), it will be retried until either it is - successful, or the implementation decides to abort. + successful, or the implementation decides to abort. There are a number of + overloaded execute methods in the + RetryOperations interface dealing with various use + cases for recovery when all retry attempts are exhausted, and also with + retry state, which allows clients and implementations to store information + between calls (more on this later). The simplest general purpose implementation of RetryOperations is RetryTemplate. It could be used like this - RetryTemplate template = new RetryTemplate(); + () { - public Object doWithRetry(RetryContext context) { + public Foo doWithRetry(RetryContext context) { // Do stuff that might fail, e.g. webservice operation return result; } -}); +});]]> 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 @@ -66,6 +80,105 @@ Object result = template.execute(new RetryCallback() { context is occasionally useful for storing data that need to be shared between calls to execute. + +
+ RecoveryCallback + + When a retry is exhausted the + RetryOperations can pass control to a different + callback, the RetryCallback. To use this feature + clients just pass in the callbacks together to the same method, for + example: + + () { + public Foo doWithRetry(RetryContext context) { + // business logic here + }, + new RecoveryCallback() { + // recover logic here + } +});]]>If the business logic does not succeed before the + template decides to abort, then the client is given the chance to do + some alternate processing through the recovery callback. +
+ +
+ Stateless Retry + + In the simplest case a retry is just a while loop - the + RetryTemplate can just keep trying until it + either succeeds or fails. The RetryContext + contains 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 + RetryPolicy (the + RetryTemplate can handle both). In a stateless + retry, the callback is always executed in the same thread on retry as + when it failed. +
+ +
+ Stateful Retry + + 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. + + In these cases a stateless retry is not good enough because the + re-throw and roll back necessarily involve leaving the + RetryOperations.execute() 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 a storage strategy + RetryContextCache which can be injected into the + RetryTemplate. The default implementation of the + RetryContextCache is in memory, using a simple + Map. Advanced usage with multiple processes in a + clustered environment might also consider implementing the + RetryContextCache with a cluster cache of some + sort (even in a clustered environment this might be overkill). + +Part of the reponsibility of the + RetryOperations is to recognise the failed + operations when they come back in a new execution (and usually wrapped + in a new transaction). To facilitate this, Spring Batch provides the + RetryState abstraction. This works in + conjunction with a special execute methods in + the RetryOperations. + + The way the failed operations are recognised is by identifying + the state across multiple invocations of the retry. To identify the + state the user can provide an RetryState + object, and this is responsible for returning a unique key identifying + the item. The identifier is used as a key in the + RetryContextCache. + + + Be very careful with the implementation of + Object.equals() and Object.hashCode() in + the key returned by RetryState. The best + advice is to use a business key to identify the items. In the case + of a JMS message the message ID can be used. + + + When the retry is exhausted there is also the option to handle + the failed item in a different way, instead of calling the + RetryCallback (which is presumed now to be + likely to fail). Just like in the stateless case, this option is + provided by the RecoveryCallback, which can be + provided by passing it in to the execute method + of RetryOperations. + + The decision to retry or not is actually delegated to a regular + retry policy, so the usual concerns about limits and timeouts can be + injected through the RetryPolicy (see + below). +
@@ -100,34 +213,19 @@ Object result = template.execute(new RetryCallback() { tight loop retrying something that you know in advance is fatal. -
- Stateless Retry + Spring Batch provides some simple general purpose implementations of + stateless RetryPolicy, for example a + SimpleRetryPolicy, and the + TimeoutRetryPolicy used in the example + above. - In the simplest case a retry is just a while loop - the - RetryTemplate can just keep trying until it - either succeeds or fails. The RetryContext - contains 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 - RetryPolicy (the - RetryTemplate can handle both). In a stateless - retry, the callback is always executed in the same thread on retry as - when it failed. + The SimpleRetryPolicy just allows a retry on + any of a named list of exception types, up to a fixed number of times. It + also has a list of "fatal" exceptions that should never be retried, and + this list overrides the retryable list, so it can be used to give finer + control over the retry behaviour, e.g. - Spring Batch provides some simple general purpose implementations - of stateless RetryPolicy, for example a - SimpleRetryPolicy, and the - TimeoutRetryPolicy used in the example - above. - - The SimpleRetryPolicy just allows a retry - on any of a named list of exception types, up to a fixed number of - times. It also has a list of "fatal" exceptions that should never be - retried, and this list overrides the retryable list, so it can be used - to give finer control over the retry behaviour, e.g. - - SimpleRetryPolicy policy = new SimpleRetryPolicy(5); + () { + public Foo doWithRetry(RetryContext context) { // business logic here } -}); +});]]> - There is also a more flexible implementation called - ExceptionClassifierRetryPolicy, which allows the - user to configure different retry behaviour for an arbitrary set of - excecption types though the ExceptionClassifier - abstraction. The policy works by calling on the classifier to convert an - exception into a delegate RetryPolicy, so for - example, one exception type can be retried more times before failure - than another by mapping it to a different policy. + There is also a more flexible implementation called + ExceptionClassifierRetryPolicy, which allows the + user to configure different retry behaviour for an arbitrary set of + excecption types though the ExceptionClassifier + abstraction. The policy works by calling on the classifier to convert an + exception into a delegate RetryPolicy, so for + example, one exception type can be retried more times before failure than + another by mapping it to a different policy. - 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. -
- -
- Stateful Retry - - 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. - - In these cases a stateless retry is not good enough because the - re-throw and roll back necessarily involve leaving the - RetryOperations.execute() 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 a storage strategy - RetryContextCache. The default implementation of - the RetryContextCache is in memory, using a - simple Map. Advanced usage with multiple - processes in a clustered environment might also consider implementing - the RetryContextCache with a cluster cache of - some sort (even in a clustered environment this might be - overkill). - -
- Item processing and stateful retry - - 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 - (like a message or message payload) is being processed, Spring Batch - provides the ItemWriterRetryPolicy. This works - in conjunction with a special RetryCallback - implementation ItemWriterRetryCallback, which - in turn relies on the user providing an - ItemWriter. This callback implements the common - pattern where it passes the item to a writer. - - The way the failed operations are recognised in this - implementation is by identifying the item across multiple invocations - of the retry. To identify the item the user can provide an - ItemKeyGenerator strategy, and this is - responsible for returning a unique key identifying the item. The - identifier is used as a key in the - RetryContextCache. An - ItemKeyGenerator can be provided either by - injecting it directly into the - ItemWriterRetryCallback, or by implementing the - interface in the ItemWriter, or by accepting - the default which is to simply use the item itself as a key. - - - If you use the default item key generation strategy be very - careful with the implementation of Object.equals() and - Object.hashCode() in your item class. In particular, if - the ItemWriter is going to insert the item - into a database and update a primary key field it is not a good idea - to use the primary key in the equals and - hashCode implementations, because their - values will change before and after the call to teh - ItemWriter. The best advice is to use a - business key to identify the items. - - - 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 - RetryCallback (which is presumed now to be - likely to fail). This option is provided by the - ItemRecoverer strategy. Like the key generator, - it can be directly injected or provided by implementing the interface - in the ItemWriter. - - 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 - ItemWriterRetryPolicy through the delegate - property. -
-
+ 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.
@@ -249,20 +264,20 @@ template.execute(new RetryCallback() { RetryTemplate can pause execution according to the BackoffPolicy in place. - public interface BackoffPolicy { + A BackoffPolicy is free to implement - the backOff in any way it chooses. The policies provided by Spring Batch - out of the box all use Object.wait(). 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 - ExponentialBackoffPolicy. +}]]>A BackoffPolicy is free to + implement the backOff in any way it chooses. The policies provided by + Spring Batch out of the box all use Object.wait(). 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 ExponentialBackoffPolicy.
@@ -279,15 +294,15 @@ template.execute(new RetryCallback() { The interface looks like this: - public interface RetryListener { + callback); - void onError(RetryContext context, RetryCallback callback, Throwable e); + void onError(RetryContext context, RetryCallback callback, Throwable e); - void close(RetryContext context, RetryCallback callback, Throwable e); + void close(RetryContext context, RetryCallback callback, Throwable e); } -The open and +]]>The open and close callbacks come before and after the entire retry in the simplest case, and onError applies to the individual RetryCallback calls. The close @@ -318,16 +333,16 @@ template.execute(new RetryCallback() { remoteCall (for more detail on how to configure AOP interceptors see the Spring User Guide): - <aop:config> - <aop:pointcut id="transactional" - expression="execution(* com...*Service.remoteCall(..))" /> - <aop:advisor pointcut-ref="transactional" - advice-ref="retryAdvice" order="-1"/> -</aop:config> + + + + -<bean id="retryAdvice" - class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor"/> - + +]]> The example above uses a default RetryTemplate inside the interceptor. To change the