3

I'm running into an issue where @CircuitBreaker is not retrying.

I have a service class (ex. Class UserService and method name getUser), this method calls another Spring bean (ex. AppClient and execute) which in turn makes call to remote service(REST call). the execute method is annoted with @CircuitBreaker of Spring-Retry.

I have exposed the call to service method (Class UserService and method name getUser) in rest controller and I test it using Postman. here is what happens - In case of timeout error, it does call @Recover method. But it wouldn't retry calling the remote service three times (default value).

If I manually run it 3 times through Postman, the circuit breaker state changes to OPEN and redirects calls to @Recover method and after reset time out, it resumes making calls to remote service.

Also, I replaced @CircuitBreaker with @Retryable and that makes call three times (default value). I'm using spring-retry version 1.2.1.RELEASE and aspectjtools version 1.6.2.

Why would it not retry with @CircuitBreaker?? I would like to have both features Circuit breaker and Retry. As per Documentation @CircuitBreaker is suppose to do both. Any help would be appreciated.

@Configuration
@EnableRetry
public class cfgUserApp
{

}

@RestController
public class UserController
{
@Autowired
UserService userService;

@RequestMapping(value = "/user/{userId}", method = RequestMethod.GET, headers = "Accept=application/json")
public ResponseEntity<User> getUser(@PathVariable String userId) {
    return ok(userService.getUser(userId));
}
}

/* Spring Bean -- userService */

public class UserServiceImpl
implements userService
{

@Override
public User getUser( final String userId )
{
checkArgument( User != null && !User.isEmpty(), "User Id can not be null or empty." );
try
{
    final HttpGet request = buildGetUserRequest( userId );
    final User userResult = appClient.execute( request,
        response -> createGetReservationResult( response ) );
    return userResult;
}
catch ( final IOException e )
{
    LOG.error( "getUser failed.", e );
    throw new AppException(e.getMessage(), e);
}
}
}

public Class Appclient {

@Recover
public <T> T recover(AppException appException, final HttpUriRequest request,
                            final ResponseHandler<T> responseFunction )
{
    System.out.println("In Recovery");
    return <T>new User();
}

@CircuitBreaker( include = AppException.class, openTimeout = 5000l, resetTimeout = 10000l )
    public <T> T execute( final HttpUriRequest request,
                          final ResponseHandler<T> responseFunction )
                          {
                          // HTTP call
                          }
}
Dilip P
  • 33
  • 1
  • 5

1 Answers1

1

It's probably a similar problem to this - annotation not found when a JDK proxy is used, since you have an interface.

Move the annotation to the interface, or use

@EnableRetry(proxyTargetClass = true)

I added another commit to that PR to fix this.

EDIT

You seem to have mis-understood @CircuitBreaker; it does not retry internally; instead it is a stateful retry interceptor that fails over after the circuit breaker properties are exceeded.

I changed your app to do this...

@GetMapping("/getnumber")
public int getNumber(){
    return this.userService.getNumber() + this.userService.getNumber() +
            this.userService.getNumber() + this.userService.getNumber();
}

and then I see

getNumber
fallback
getNumber
fallback
getNumber
fallback
fallback

To achieve what (I think) you want, you need to wrap the service with a retrying service, and put the recovery there:

@SpringBootApplication
@EnableRetry(proxyTargetClass = true)
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@RestController
class UserRestController {

    private final RetryingUserService userService;

    @Autowired
    public UserRestController(RetryingUserService userService) {
        this.userService = userService;
    }

    @GetMapping("/getnumber")
    public int getNumber() {
        return this.userService.getNumber();
    }

}

@Service
class RetryingUserService {

    private final UserService userService;

    public RetryingUserService(UserService userService) {
        this.userService = userService;
    }

    @Retryable
    public int getNumber() {
        return this.userService.getNumber();
    }

    @Recover
    public int fallback(RuntimeException re) {
        System.out.println("fallback");
        return 2;
    }

}

@Service
class UserService {

    @CircuitBreaker(include = RuntimeException.class)
    public int getNumber() {
        System.out.println("getNumber");
        throw new RuntimeException();
    }

}

and

getNumber
getNumber
getNumber
fallback

Or, you might want to put the retry within the circuit breaker, depending on what behavior you want.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Thank you for your suggestion. I tried to move the annotation (Circuitbreaker and Recover) to the interface and also tried setting the proxyTargetClass to true. None of these solutions work. If i replace Circuitbreaker with Retryable it retries. But I loose Circuit breaker feature. – Dilip P Oct 23 '18 at 18:12
  • Post a small, complete, project someplace that exhibits this behavior and I'll take a look. – Gary Russell Oct 23 '18 at 20:19
  • here is the sample application: https://github.com/pdilip1/spring-circuit-breaker. I'm intentionally throwing an exception from service method to trigger circuit breaker. As I mentioned in my post, circuit breaker is not retrying but if I change circuitbreaker to retryable, it retries. Please look at DemoApplication.java (spring boot main application class), this class also has controller and service class. – Dilip P Oct 24 '18 at 17:45
  • You have misunderstood how the circuit breaker works; it is a stateful retry interceptor which can go open after some number of calls fails. See the edit to my answer. – Gary Russell Oct 24 '18 at 19:54
  • Thank you for the clarification. The solution you provided to achieve both (circuit breaker and retry) works great! – Dilip P Oct 24 '18 at 20:08
  • @GaryRussell do you have a sample with the retry within the circuit breaker? – Rogelio Blanco Jun 08 '21 at 02:24
  • I don't, but it should be similar to the above, with the annotations reversed. If you can't get it to work, ask a new question showing the code that doesn't work. Extending this question/answer for a different use case is not appropriate. – Gary Russell Jun 08 '21 at 13:05