Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RestTemplate set timeout per request

I have a @Service with several methods, each method consumes a different web api. Each call should have a custom read timeout. Is it thread-safe to have one RestTemplate instance and change the timeout via the factory in each method like so

((HttpComponentsClientHttpRequestFactory)restTemplate.getRequestFactory())
.setReadTimeout(customMillis);

My concern is that I'm changing the timeout on the factory and its not like a RequestConfig. Will this approach be thread-safe considering these methods might get called by multiple users at the same time? Or each method should have its own RestTemplate?

like image 837
prettyvoid Avatar asked Jan 20 '18 22:01

prettyvoid


3 Answers

Option 1: More than one RestTemplate

If you are changing the properties of the connections created, you will need to have one RestTemplate per configuration. I had this very same problem recently and had two versions of RestTemplate, one for "short timeout" and one for "long timeout". Within each group (short/long) I was able to share that RestTemplate.

Having your calls change the timeout settings, create a connection, and hope for the best is a race condition waiting to happen. I would play this safe and create more than one RestTemplate.

Example:

@Configuration
public class RestTemplateConfigs {
    @Bean("shortTimeoutRestTemplate")
    public RestTemplate shortTimeoutRestTemplate() {
       // Create template with short timeout, see docs.
    }
    @Bean("longTimeoutRestTemplate")
    public RestTemplate longTimeoutRestTemplate() {
       // Create template with short timeout, see docs.
    }
}

And then you can wire them in to your services as needed:

@Service
public class MyService {
    private final RestTemplate shortTimeout;
    private final RestTemplate longTimeout;

    @Autowired
    public MyService(@Qualifier("shortTimeoutRestTemplate") RestTemplate shortTimeout, 
                     @Qualifier("longTimeoutRestTemplate") RestTemplate longTimeout) {
        this.shortTimeout = shortTimeout;
        this.longTimeout = longTimeout;
    }

    // Your business methods here...
}

Option 2: Wrap calls in a Circuit Breaker

If you are calling out to external services, you probably should be using a circuit breaker for this. Spring Boot works well with Hystrix, a popular implementation of the circuit breaker pattern. Using hystrix you can control the fallback for each service you call out to, and the timeouts.

Suppose you have two options for Service A: 1) Cheap but sometimes slow 2) Expensive but fast. You can use Hystrix to give up on Cheap/Slow and use Expensive/Fast when you really need to. Or you can have no backup and just have Hystrix call a method that provides a sensible default.

Untested example:

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

@Service
public class MyService {
    private final RestTemplate restTemplate;

    public BookService(RestTemplate rest) {
        this.restTemplate = rest;
    }

    @HystrixCommand(
        fallbackMethod = "fooMethodFallback",
        commandProperties = { 
            @HystrixProperty(
                 name = "execution.isolation.thread.timeoutInMilliseconds", 
                 value="5000"
            )
        }
    )
    public String fooMethod() {
        // Your logic here.
        restTemplate.exchange(...); 
    }

    public String fooMethodFallback(Throwable t) {
        log.error("Fallback happened", t);
        return "Sensible Default Here!"
    }
}

The fallback method has options too. You could annotate that method with @HystrixCommand and attempt another service call. Or, you could just provide a sensible default.

like image 186
Todd Avatar answered Nov 17 '22 07:11

Todd


Changing timeouts from the factory after RestTemplate initialization is just a race condition waiting to occur (Like Todd explained). RestTemplate was really designed to be built with pre-configured timeouts and for those timeouts to stay untouched after initialization. If you use Apache HttpClient then yes you can set a RequestConfig per request and that is the proper design in my opinion.

We are already using RestTemplate everywhere in our project and we can't really afford the refactoring at the moment, that an http client switch would ensue.

For now I ended up with a RestTemplate pooling solution, I created a class called RestTemplateManager and I gave it all responsibility of creating templates and pooling them. This manager have a local cache of templates grouped by service and readTimeout. Imagine a cache hashmap with the following structure:

ServiceA|1000 -> RestTemplate

ServiceA|3000 -> RestTemplate

ServiceB|1000 -> RestTemplate

The number in the key is the readTimeout in milliseconds (key can be adapted to support more than readTimeout later on). So when ServiceA requests a template with 1000ms read timeout, the manager will return the cached instance, if it doesn't exist it will be created and returned.

In this approach I saved myself from pre-defining RestTemplates, I only have to request a RestTemplate from the manager above. This also keeps initializations at a minimum.

This shall do until I have the time to ditch RestTemplate and use a more appropriate solution.

like image 6
prettyvoid Avatar answered Nov 17 '22 05:11

prettyvoid


I assume you want read timeouts in case the response takes too long.

A possible solution would be to implement the timeout yourself by canceling the request if it hasn't completed in the given time.

To achieve this, you could use an AsyncRestTemplate instead, which has builtin support for async operations like timeout and cancellation.

This gives you more control over the timeout for each request, example:

ListenableFuture<ResponseEntity<Potato>> future =
                asyncRestTemplate.getForEntity(url, Potato.class);

ResponseEntity<Potato> response = future.get(5, TimeUnit.SECONDS);
like image 4
ESala Avatar answered Nov 17 '22 05:11

ESala