spring-retry
spring-retry copied to clipboard
Spring Retry + Hystrix issue
I have a @HystrixCommand annotated method that makes a restTemplate call. I also have another one that makes a SOAP call. When an exception is thrown inside the hystrixcommand annotated method, it returns a HystrixRuntimeException, and nested in that exception is the actual exception that was thrown, for instance httpClientErrorException, socketTimeout, or SoapFault.
I call these hystrixCommands from within a Spring RetryTemplate, but the problem is that I am having trouble make the RetryTemplate only retry on certain exceptions that are the nested exceptions within the HystrixRuntimeException.
The scenario now is such that, Spring retryTemplate will only retry if there is a HystrixRuntime Exception, I want to retry based on the excpetions nested within the hystrixRuntime Exception.
AccountClient.java
@Service
public class AccountClient {
@Inject
private Properties properties;
@Inject
private RestTemplate restTemplate;
@HystrixCommand()
public Account getAccount(String accountId){
String url = properties.getAccountBaseUrl() + properties.getGetAccountEndpoint() + "accountId={accountId}";
ResponseEntity<Account> response = restTemplate.getForEntity(url, Account.class, accountId);
return response.getBody();
}
}
AccountService.java
@Service
public class AccountService {
@Inject
private AccountClient accountClient;
@Inject
private RetryTemplate retryTemplate;
private static final Logger log = LoggerFactory.getLogger(AccountService.class);
public Account getAccount(String accountId){
Account account = retryTemplate.execute(new RetryCallback<Account, RuntimeException>() {
public Account doWithRetry(RetryContext context) {
log.info("Retry Attempt: " + context.getRetryCount() + " Exception thrown: " + context.getLastThrowable());
Account account = accountClient.getAccount(accountId);
return account;
}
});
return account;
}
}
RetryTemplate bean in @Configuration class
@Bean
public RetryTemplate retryTemplate() {
Map<Class<? extends Throwable>, Boolean> includeExceptions = new HashMap<>();
String[] exceptions = properties.getExceptions().split(" ");
List<String> exceptionz = Arrays.asList(exceptions);
for (String e : exceptionz) {
Class<Exception> exc = null;
try {
exc = (Class<Exception>) Class.forName(e);
} catch (ClassNotFoundException ex) {
// ex.printStackTrace(); TODO log here
}
includeExceptions.put(exc, true);
}
SimpleRetryPolicy policy = new SimpleRetryPolicy(properties.getMaxRetryAttempts(), includeExceptions);
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
return template;
}
Nesting @HystrixCommand
inside a retry callback is somewhat of an odd thing to do (Hystrix has retry built in). You could fix it with a custom exception classifier if you need to, but I reckon it's better to choose one or the other (spring-retry, or hystrix). Hystrix has more features, so they aren't interchangeable (but note that @CircuitBreaker
is coming in spring-retry 1.2.0.
[...] Hystrix has retry built in [...]
@dsyer could you plz specify? I never came around a first-class citizen in Hystrix that would allow me to configure retries properly... also Netflix states:
Hystrix treats the execution that gets wrapped as a black-box. Because only some executions benefit from a retry policy, Hystrix does not add the complexity of retry.
I guess I was confused. We use Hystrix with Ribbon/Feign/Zuul so much I probably thought the retry features were in the former not the latter. If you work out how to customize the exception classifier so it works please consider sending a pull request (here or to spring-cloud-netflix, if it is Hystrix specific). Or use @CircuitBreaker
instead.
I noticed that SimpleRetryPolicy
has this constructor public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions, boolean traverseCauses)
. If the exception is nested within the HystrixRuntimeException
then if you set traverseCauses
then it might work.