

Timeouts with Spring Boot and Resilience4j - Reflectoring
source link: https://reflectoring.io/time-limiting-with-springboot-resilience4j/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In this series so far, we have learned how to use the Resilience4j Retry, RateLimiter, TimeLimiter, Bulkhead, Circuitbreaker core modules and also seen its Spring Boot support for the Retry and the RateLimiter modules.
In this article, we’ll focus on the TimeLimiter and see how the Spring Boot support makes it simple and more convenient to implement time limiting in our applications.
Code Example
This article is accompanied by a working code example on GitHub.
High-level Overview
If you haven’t read the previous article on the TimeLimiter, check out the “What is Time Limiting?”, “When to Use TimeLimiter?”, and “Resilience4j TimeLimiter Concepts” sections for a quick intro.
You can find out how to set up Maven or Gradle for your project here.
Using the Spring Boot Resilience4j TimeLimiter Module
We will use the same example as the previous articles in this series. Assume that we are building a website for an airline to allow its customers to search for and book flights. Our service talks to a remote service encapsulated by the class FlightSearchService
.
Let’s see how to use the various features available in the TimeLimiter module. This mainly involves configuring the TimeLimiter
instance in the application.yml
file and adding the @TimeLimiter
annotation on the Spring @Service
component that invokes the remote operation.
Basic Example
Let’s say we want to set a time limit of 2s for the flight search call. In other words, if the call doesn’t complete within 2s, we want to be notified through an error.
First, we will configure the TimeLimiter
instance in the application.yml
file:
resilience4j:
instances:
basicExample:
timeoutDuration: 2s
Next, let’s add the @TimeLimiter
annotation on the method in the bean that calls the remote service:
@TimeLimiter(name = "basicExample")
CompletableFuture<List<Flight>> basicExample(SearchRequest request) {
return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlights(request));
}
Here, we can see that the remote operation is being invoked asynchronously, with the basicExample()
method returning a CompletableFuture
to its caller.
Finally, let’s call the time-limited basicExample()
method from a different bean:
SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021");
System.out.println("Calling search; current thread = " + Thread.currentThread().getName());
CompletableFuture<List<Flight>> results = service.basicExample(request);
results.whenComplete((result, ex) -> {
if (ex != null) {
System.out.println("Exception " +
ex.getMessage() +
" on thread " +
Thread.currentThread().getName() +
" at " +
LocalDateTime.now().format(formatter));
}
if (result != null) {
System.out.println(result + " on thread " + Thread.currentThread().getName());
}
});
Here’s sample output for a successful flight search that took less than the 2s timeoutDuration
we specified:
Calling search; current thread = main
Searching for flights; current time = 13:13:55 705; current thread = ForkJoinPool.commonPool-worker-3
Flight search successful at 13:13:56 716
[Flight{flightNumber='XY 765', flightDate='10/30/2021', from='NYC', to='LAX'}, ... }] on thread ForkJoinPool.commonPool-worker-3
The output shows that the search was called from the main thread, and executed on a different thread.
And this is sample output for a flight search that timed out:
Calling search; current thread = main
Searching for flights; current time = 13:16:03 710; current thread = ForkJoinPool.commonPool-worker-3
Exception java.util.concurrent.TimeoutException: TimeLimiter 'timeoutExample' recorded a timeout exception. on thread pool-2-thread-1 at 13:16:04 215
java.util.concurrent.CompletionException: java.util.concurrent.TimeoutException: TimeLimiter 'timeoutExample' recorded a timeout exception.
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:331)
... other lines omitted ...
Flight search successful at 13:16:04 719
The timestamps and thread names above show that the caller got a TimeoutException
even as the asynchronous operation finished later on a different thread.
Specifying a Fallback Method
Sometimes we may want to take a default action when a request times out. For example, if we are not able to fetch a value from a remote service in time, we may want to return a default value or some data from a local cache.
We can do this by specifying a fallbackMethod
in the @TimeLimiter
annotation:
@TimeLimiter(name = "fallbackExample", fallbackMethod = "localCacheFlightSearch")
CompletableFuture<List<Flight>> fallbackExample(SearchRequest request) {
return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlights(request));
}
The fallback method should be defined in the same bean as the time-limiting bean. It should have the same method signature as the original method with one additional parameter - the Exception
that caused the original one to fail:
private CompletableFuture<List<Flight>> localCacheFlightSearch(SearchRequest request, TimeoutException rnp) {
// fetch results from the cache
return results;
}
Here’s sample output showing the results being fetched from a cache:
Calling search; current thread = main
Searching for flights; current time = 08:58:25 461; current thread = ForkJoinPool.commonPool-worker-3
TimeLimiter 'fallbackExample' recorded a timeout exception.
Returning search results from cache
[Flight{flightNumber='XY 765', flightDate='10/30/2021', from='NYC', to='LAX'}, ... }] on thread pool-2-thread-2
Flight search successful at 08:58:26 464
TimeLimiter Events
The TimeLimiter
has an EventPublisher
which generates events of the types TimeLimiterOnSuccessEvent
, TimeLimiterOnErrorEvent
, and TimeLimiterOnTimeoutEvent
. We can listen to these events and log them, for example.
However, since we don’t have a reference to the TimeLimiter
instance when working with Spring Boot Resilience4j, this requires a little more work. The idea is still the same, but how we get a reference to the TimeLimiterRegistry
and then the TimeLimiter
instance itself is a bit different.
First, we @Autowire
a TimeLimiterRegistry
into the bean that invokes the remote operation:
@Service
public class TimeLimitingService {
@Autowired
private FlightSearchService remoteSearchService;
@Autowired
private TimeLimiterRegistry timeLimiterRegistry;
// other lines omitted
}
Then we add a @PostConstruct
method which sets up the onSuccess
and onFailure
event handlers:
@PostConstruct
void postConstruct() {
EventPublisher eventPublisher = timeLimiterRegistry.timeLimiter("eventsExample").getEventPublisher();
eventPublisher.onSuccess(System.out::println);
eventPublisher.onError(System.out::println);
eventPublisher.onTimeout(System.out::println);
}
Here, we fetched the TimeLimiter
instance by name from the TimeLimiterRegistry
and then got the EventPublisher
from the TimeLimiter
instance.
Instead of the @PostConstruct
method, we could have also done the same in the constructor of TimeLimitingService
.
Now, the sample output shows details of the events:
Searching for flights; current time = 13:27:22 979; current thread = ForkJoinPool.commonPool-worker-9
Flight search successful
2021-10-03T13:27:22.987258: TimeLimiter 'eventsExample' recorded a successful call.
Search 3 successful, found 2 flights
Searching for flights; current time = 13:27:23 279; current thread = ForkJoinPool.commonPool-worker-7
Flight search successful
2021-10-03T13:27:23.280146: TimeLimiter 'eventsExample' recorded a successful call.
... other lines omitted ...
2021-10-03T13:27:24.290485: TimeLimiter 'eventsExample' recorded a timeout exception.
... other lines omitted ...
Searching for flights; current time = 13:27:24 334; current thread = ForkJoinPool.commonPool-worker-3
Flight search successful
TimeLimiter Metrics
Spring Boot Resilience4j makes the details about the last one hundred timelimit events available through Actuator endpoints:
/actuator/timelimiters
/actuator/timelimiterevents
/actuator/metrics/resilience4j.ratelimiter.waiting_threads
Let’s look at the data returned by doing a curl
to these endpoints.
/timelimiters
Endpoint
This endpoint lists the names of all the time-limiter instances available:
$ curl http://localhost:8080/actuator/timelimiters
{
"timeLimiters": [
"basicExample",
"eventsExample",
"timeoutExample"
]
}
timelimiterevents
Endpoint
This endpoint provides details about the last 100 time limit events in the application:
$ curl http://localhost:8080/actuator/timelimiterevents
{
"timeLimiterEvents": [
{
"timeLimiterName": "eventsExample",
"type": "SUCCESS",
"creationTime": "2021-10-07T08:19:45.958112"
},
{
"timeLimiterName": "eventsExample",
"type": "SUCCESS",
"creationTime": "2021-10-07T08:19:46.079618"
},
... other lines omitted ...
{
"timeLimiterName": "eventsExample",
"type": "TIMEOUT",
"creationTime": "2021-10-07T08:19:47.908422"
},
{
"timeLimiterName": "eventsExample",
"type": "TIMEOUT",
"creationTime": "2021-10-07T08:19:47.909806"
}
]
}
Under the timelimiterevents
endpoint, there are two more endpoints available: /actuator/timelimiterevents/{timelimiterName}
and /actuator/timelimiterevents/{timeLimiterName}/{type}
. These provide similar data as the above one, but we can filter further by the retryName
and type
(success
/timeout
).
calls
Endpoint
This endpoint exposes the resilience4j.timelimiter.calls
metric:
$ curl http://localhost:8080/actuator/metrics/resilience4j.timelimiter.calls
{
"name": "resilience4j.timelimiter.calls",
"description": "The number of successful calls",
"baseUnit": null,
"measurements": [
{
"statistic": "COUNT",
"value": 12
}
],
"availableTags": [
{
"tag": "kind",
"values": [
"timeout",
"successful",
"failed"
]
},
{
"tag": "name",
"values": [
"eventsExample",
"basicExample",
"timeoutExample"
]
}
]
}
Conclusion
In this article, we learned how we can use Resilience4j’s TimeLimiter module to set a time limit on asynchronous, non-blocking operations. We learned when to use it and how to configure it with some practical examples.
You can play around with a complete application illustrating these ideas using the code on GitHub.
Recommend
-
26
内容纲要 Resilience4j是一个轻量级、易于使用的容错库,其灵感来自Netflix Hystrix,但专为Java 8和函数式编程设计。轻量级,因为库只使用Vavr,它没有任何其他外部库依赖项。相比之下,Netflix Hystrix对Archaius有一...
-
10
In this article I’m going to describe two features of Spring Cloud Gateway: retrying based on GatewayFilter pattern and timeouts based on a global configuration. In some previous articles in this series I have described rate limi...
-
9
使用Spring WebClient发送HTTP请求Spring 5有一个响应式 Web 框架:Spring WebFlux。这旨在与现有的 Spring Web MVC API 共存,但增加对非阻塞设计的支持。使用 WebFlux,您可以构建异步 Web 应用程序,使用反应式流和函数式 API 来更好地支持并发和扩展。
-
7
With the @SpringBootTest annotation, Spring Boot provides a convenient way to start up an application context to be used in a test. In this tutorial, we’ll discuss when to use @SpringBootTest and when to better use o...
-
43
Implementing Retry with Resilience4j and Spring BootIn this series so far, we have learned how to use the Resilience4j Retry,
-
6
Bean Validation is the de-facto standard for implementing validation logic in the Java ecosystem. It’s well integrated with Spring and Spring Boot. However, there are some pitfalls. This tutor...
-
13
In this series so far, we’ve learned how to use the Resilience4j Retry, RateLimiter,
-
10
Publishing Metrics from Spring Boot to Amazon CloudWatchMetrics provide a quantifiable measure of specific attributes of an application. A collection of different metrics give intelligent insights into the health and performance of an applica...
-
7
Feature Flags with Spring BootFeature flags are a great tool to improve confidence in deployments and to avoid impacting customers with unintended changes. Instead of deploying a new feature directly to production, we “hide” it behind...
-
9
Internationalization is the process of making an application adaptable to multiple languages and regions without major changes in the source code. In this tutorial, we will understand the concepts of internationalization, and illustra...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK