introduction
In distributed systems, problems such as network delay and temporary unavailability often occur, resulting in operation failure. These temporary failures can usually be resolved by retrying.
The Spring framework provides SpringRetry module, which implements a powerful retry mechanism to help developers handle these temporary errors gracefully.
1. Basic knowledge of SpringRetry
SpringRetry is a component in the Spring ecosystem dedicated to handling retryable operations. It provides declarative retry support, allowing developers to add retry capabilities to methods in a non-invasive way. The core idea of SpringRetry is to separate the retry logic from the business logic, making the code clearer and maintainable.
To use SpringRetry, you need to add relevant dependencies to the project first. For Maven projects, the following dependencies can be added:
<!-- SpringRetryrely --> <dependency> <groupId></groupId> <artifactId>spring-retry</artifactId> <version>1.3.3</version> </dependency> <!-- SpringRetry需要relyAOP --> <dependency> <groupId></groupId> <artifactId>spring-aspects</artifactId> </dependency>
In Spring Boot project, you can use spring-boot-starter-aop directly, which already contains the required AOP dependencies:
<dependency> <groupId></groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2. Use Spring Retry
Before using SpringRetry, you need to enable it in your app.
In Spring Boot app, just add the @EnableRetry annotation to the main class or configuration class:
import ; import ; import ; @SpringBootApplication @EnableRetry // Use Spring Retry functionpublic class RetryDemoApplication { public static void main(String[] args) { (, args); } }
The @EnableRetry annotation causes Spring to create a section, intercept all method calls with the @Retryable annotation, and retry according to the configuration when the method call fails.
This AOP-based implementation makes the retry logic completely transparent to business code and conforms to the design principle of separation of concerns.
3. Detailed explanation of @Retryable annotation
@Retryable is a core annotation provided by SpringRetry, which is used to mark methods that need to be retryed. When a method with @Retryable annotation throws an exception, SpringRetry retrys based on the configured policy.
import ; import ; @Service public class RemoteServiceClient { @Retryable( value = {}, // Specify the type of exception that triggers retry maxAttempts = 3, // Maximum number of retry (including the first call) backoff = @Backoff(delay = 1000, multiplier = 2) // Backoff strategy ) public String callRemoteService(String param) { // Simulate remote service calls, an exception may be thrown ("Calling remote service with parameter: " + param); if (() > 0.7) { return "Success response"; } else { throw new ServiceTemporaryException("Service temporarily unavailable"); } } } // Custom temporary service exceptionclass ServiceTemporaryException extends RuntimeException { public ServiceTemporaryException(String message) { super(message); } }
The @Retryable annotation supports multiple attribute configurations that define the behavior of retry:
-
value
/include
: Specify which exception types should trigger retry -
exclude
: Specify which exception types should not trigger retry -
maxAttempts
: Maximum number of attempts, default is 3 -
backoff
: Define a backoff strategy for retry interval
In a production environment, placing these parameters rationally is crucial to implementing an effective retry mechanism. For example, for network requests, a longer retry interval may be required; for memory operations, only a very short interval may be required.
4. Retry the backoff strategy (Backoff)
The retry rollback strategy controls the waiting time between retry. SpringRetry provides the @Backoff annotation to configure the fallback policy, which is usually used with @Retryable.
import ; import ; import ; @Service public class ExternalAPIClient { @Retryable( value = {}, maxAttempts = 4, backoff = @Backoff( delay = 1000, // Initial delay time (milliseconds) multiplier = 2, // Delay multiple maxDelay = 10000 // Maximum delay time (milliseconds) ) ) public String fetchData() { // Calling the implementation of external API ("Attempting to fetch data from external API at " + ()); double random = (); if (random < 0.8) { throw new APIException("API temporarily unavailable"); } return "Data successfully fetched"; } } class APIException extends RuntimeException { public APIException(String message) { super(message); } }
In the example above, the retry interval will grow exponentially: wait 1 second after the first failure, wait 2 seconds after the second failure, and wait 4 seconds after the third failure. This exponential backoff strategy is especially useful when dealing with services that may fail due to overload, as it allows more recovery time for the service.
The main properties of @Backoff annotation include:
-
delay
: Initial delay time (milliseconds) -
multiplier
: Multiplier factor of delay time -
maxDelay
: Maximum delay time (milliseconds) -
random
: Whether to add randomness (avoid the "shocking effect" caused by multiple clients retrying simultaneously)
5. Recovery method (@Recover)
When the retry reaches the maximum number of times, SpringRetry provides the @Recover annotation to define the recovery method. The recovery method must be in the same class as the @Retryable method and has a compatible return type and parameter list.
import ; import ; import ; @Service public class PaymentService { @Retryable( value = {}, maxAttempts = 3 ) public String processPayment(String orderId, double amount) { ("Processing payment for order: " + orderId + ", amount: " + amount); // Simulated payment processing, sometimes fails if (() < 0.7) { throw new PaymentException("Payment gateway timeout"); } return "Payment successful"; } @Recover public String recoverPayment(PaymentException e, String orderId, double amount) { // Execute recovery logic when retry exhausts ("All retries failed for order: " + orderId); // You can record logs, send notifications or perform alternate operations return "Payment processing failed after multiple attempts. Please try again later."; } } class PaymentException extends RuntimeException { public PaymentException(String message) { super(message); } }
The first parameter of the @Recover method must be the exception type that triggers the retry, and the subsequent parameters should be consistent with the parameter list of the @Retryable method. When all retry fails, SpringRetry automatically calls the recovery method and passes the last exception as the first parameter.
The recovery method is an elegant failure handling mechanism, which can be used to implement functions such as downgrade services, record detailed error information, and send alert notifications, ensuring that the system can still handle and respond gracefully even after a retry failure.
6. Customize the retry strategy
In addition to using annotation configuration, SpringRetry also supports programming definition of more complex retry policies. This is especially useful for scenarios where retry behavior needs to be adjusted dynamically.
import ; import ; import ; import ; import ; import ; import ; import ; @Configuration public class RetryConfiguration { @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); // Configure the retry policy Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>(); (, true); (, true); (, false); RetryPolicy retryPolicy = new SimpleRetryPolicy(5, retryableExceptions); (retryPolicy); // Configure backoff policy ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); (1000); (2.0); (10000); (backOffPolicy); return template; } } // Example using RetryTemplate@Service public class DataService { private final RetryTemplate retryTemplate; @Autowired public DataService(RetryTemplate retryTemplate) { = retryTemplate; } public String fetchData() { return (context -> { // Execute possible failures here ("Attempt number: " + ()); if (() < 0.7) { throw new NetworkException("Network connection failed"); } return "Data fetched successfully"; }); } } class NetworkException extends RuntimeException { public NetworkException(String message) { super(message); } } class DatabaseException extends RuntimeException { public DatabaseException(String message) { super(message); } } class UnrecoverableException extends RuntimeException { public UnrecoverableException(String message) { super(message); } }
Using RetryTemplate, you can create highly customized retry behaviors, including:
- Configure different retry policies for different types of exceptions
- Implement custom RetryPolicy and BackOffPolicy
- Store and access status information in the retry context
- Listen to various events during retry
Although programming configuration is more complex than annotation, it provides greater flexibility and is suitable for scenarios with special needs.
7. Best practices for retrying strategies
In practical applications, correctly configuring the retry strategy is crucial to the stability and performance of the system. Here are some best practices for SpringRetry usage:
import ; import ; import ; import ; @Service public class BestPracticeService { @Retryable( value = {}, maxAttempts = 3, exclude = {}, backoff = @Backoff(delay = 2000, multiplier = 1.5, random = true) ) public String serviceOperation(String input) { ("Performing operation with input: " + input); // Simulate business logic double chance = (); if (chance < 0.4) { throw new TransientException("Temporary failure"); } else if (chance < 0.5) { throw new PermanentException("Permanent failure"); } return "Operation completed successfully"; } @Recover public String fallbackMethod(TransientException e, String input) { ("All retries failed for input: " + input); // Implement the downgrade logic return "Using fallback response for: " + input; } } class TransientException extends RuntimeException { public TransientException(String message) { super(message); } } class PermanentException extends RuntimeException { public PermanentException(String message) { super(message); } }
When designing a retry strategy, the following points should be considered:
- Distinguish between temporary and permanent failures: Retry only temporary failures that may be recovered by themselves, and avoid meaningless retry of permanent failures.
- Set a reasonable number of retries: Too many retries may exacerbate system load, while too few retries may not effectively deal with temporary failures.
- Use the appropriate backoff strategy: Exponential backoff is usually more effective than fixed intervals, which can give the system enough recovery time.
- Adding randomness: Adding random factors in the retry interval can prevent the "shocking effect" caused by multiple clients retry simultaneously.
- Set the timeout mechanism: Set a reasonable timeout time for each attempt to avoid affecting the execution of the overall retry strategy due to a single operation being stuck.
Summarize
SpringRetry provides Java applications with a powerful and flexible retry mechanism. Through @Retryable annotation and related configurations, developers can add retry capabilities to methods in a non-invasive way.
This article details the basic use of SpringRetry, the configuration of @Retryable annotation, retry rollback policy, recovery method, and custom retry strategy, and provides relevant best practice suggestions. Using SpringRetry can significantly improve the stability of distributed systems, allowing applications to gracefully handle temporary failures.
In actual applications, developers should reasonably configure retry strategies based on specific scenarios and needs, ensuring that the system can effectively deal with temporary failures, and avoid negative impacts on the system due to excessive retry.
The above is personal experience. I hope you can give you a reference and I hope you can support me more.