From 154453808d2690ce78de9def3857adfc96de1d46 Mon Sep 17 00:00:00 2001 From: Saajan Date: Mon, 30 Aug 2021 16:43:47 +0530 Subject: [PATCH] Initial commit of code for Spring Boot Resilience4j RateLimiter article --- .../springboot-resilience4j/README.md | 1 + .../springboot/RateLimiterExamplesRunner.java | 131 ++++++++++++++++++ .../springboot/RateLimitingService.java | 128 +++++++++++++++++ .../SpringbootResilience4jApplication.java | 4 + .../src/main/resources/application.yml | 55 ++++++++ 5 files changed, 319 insertions(+) create mode 100644 resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimiterExamplesRunner.java create mode 100644 resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimitingService.java diff --git a/resilience4j/springboot-resilience4j/README.md b/resilience4j/springboot-resilience4j/README.md index f2f1201..1c89564 100644 --- a/resilience4j/springboot-resilience4j/README.md +++ b/resilience4j/springboot-resilience4j/README.md @@ -5,3 +5,4 @@ Run the SpringbootResilience4jApplication program ## Blog posts * [Implementing Retry with Spring_Boot_Resilience4j](https://reflectoring.io/retry-with-springboot-resilience4j/) +* [Implementing Rate Limiting with Spring_Boot_Resilience4j](https://reflectoring.io/rate-limiting-with-springboot-resilience4j/) diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimiterExamplesRunner.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimiterExamplesRunner.java new file mode 100644 index 0000000..9318825 --- /dev/null +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimiterExamplesRunner.java @@ -0,0 +1,131 @@ +package io.reflectoring.resilience4j.springboot; + +import io.reflectoring.resilience4j.springboot.model.SearchRequest; +import java.time.Duration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class RateLimiterExamplesRunner { + + @Autowired + private RateLimitingService service; + + public static void main(String[] args) { + RateLimiterExamplesRunner runner = new RateLimiterExamplesRunner(); + runner.run(); + } + + public void run() { + System.out.println("Running ratelimiter examples"); + + System.out.println("----------------------------- basicExample ------------------------------------------"); + basicExample(); + System.out.println("-----------------------------------------------------------------------"); + + System.out.println("----------------------------- timeoutExample ------------------------------------------"); + timeoutExample(); + System.out.println("-----------------------------------------------------------------------"); + + System.out.println("------------------------------ multipleLimits_2rps_40rpm_sequential -----------------------------------------"); + multipleLimits_2rps_40rpm_sequential(); + System.out.println("-----------------------------------------------------------------------"); + + System.out.println("------------------------------- changeLimitsExample ----------------------------------------"); + changeLimitsExample(); + System.out.println("-----------------------------------------------------------------------"); + + System.out.println("------------------------------- retryAndRateLimit ----------------------------------------"); + retryAndRateLimit(); + System.out.println("-----------------------------------------------------------------------"); + + System.out.println("------------------------------ rateLimiterEvents -----------------------------------------"); + rateLimiterEvents(); + System.out.println("-----------------------------------------------------------------------"); + + System.out.println("----------------------------- fallbackExample ------------------------------------------"); + fallbackExample(); + System.out.println("-----------------------------------------------------------------------"); + } + + private void rateLimiterEvents() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + + try { + System.out.println(service.rateLimiterEventsExample(request)); + System.out.println(service.rateLimiterEventsExample(request)); + } + catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + private void retryAndRateLimit() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + + System.out.println(service.retryAndRateLimit(request)); + System.out.println(service.retryAndRateLimit(request)); + } + + private void changeLimitsExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + + for (int i=0; i<6; i++) { + System.out.println(service.changeLimitsExample(request)); + } + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + service.updateRateLimits("changeLimitsExample", 2, Duration.ofSeconds(2)); + System.out.println("Rate limits changed"); + + for (int i=0; i<6; i++) { + System.out.println(service.changeLimitsExample(request)); + } + } + + private void multipleLimits_2rps_40rpm_sequential() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + for (int i=0; i<45; i++) { + try { + System.out.println(service.multipleRateLimitsExample(request)); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void timeoutExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + try { + for (int i=0; i<3; i++) { + System.out.println(service.timeoutExample(request)); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private void basicExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + for (int i=0; i<4; i++) { + try { + System.out.println(service.basicExample(request)); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void fallbackExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "08/15/2021"); + System.out.println(service.fallbackExample(request)); + System.out.println(service.fallbackExample(request)); + } +} \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimitingService.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimitingService.java new file mode 100644 index 0000000..3b93efb --- /dev/null +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/RateLimitingService.java @@ -0,0 +1,128 @@ +package io.reflectoring.resilience4j.springboot; + +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.RequestNotPermitted; +import io.github.resilience4j.ratelimiter.annotation.RateLimiter; +import io.github.resilience4j.retry.RetryRegistry; +import io.github.resilience4j.retry.annotation.Retry; +import io.reflectoring.resilience4j.springboot.model.Flight; +import io.reflectoring.resilience4j.springboot.model.SearchRequest; +import io.reflectoring.resilience4j.springboot.services.FlightSearchService; +import java.time.Duration; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class RateLimitingService { + @Autowired + private FlightSearchService remoteSearchService; + + @Autowired + private RPMRateLimitedFlightSearchSearch rpmRateLimitedFlightSearchSearch; + + @Autowired + private RateLimiterRegistry registry; + + @Autowired + private RetryRegistry retryRegistry; + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss SSS"); + + @RateLimiter(name = "basicExample") + List basicExample(SearchRequest request) { + return remoteSearchService.searchFlights(request); + } + + @RateLimiter(name = "timeoutExample") + List timeoutExample(SearchRequest request) { + return remoteSearchService.searchFlights(request); + } + + @RateLimiter(name = "multipleRateLimiters_rps_limiter") + List multipleRateLimitsExample(SearchRequest request) { + return rpmRateLimitedFlightSearchSearch.searchFlights(request, remoteSearchService); + } + +// doesn't work - @RateLimiter is not a repeatable annotation +// @RateLimiter(name = "multipleRateLimiters_rps_limiter") +// @RateLimiter(name = "multipleRateLimiters_rpm_limiter") +// List multipleRateLimitsExample(SearchRequest request) { +// return remoteSearchService.searchFlights(request, remoteSearchService); +// } + +// doesn't work - calls within a Spring bean don't go thru the Spring proxy +// @RateLimiter(name = "multipleRateLimiters_rps_limiter") +// List rpsLimitedSearch(SearchRequest request) { +// return rpmLimitedSearch(request, remoteSearchService); +// } + +// @RateLimiter(name = "multipleRateLimiters_rpm_limiter") +// List rpmLimitedSearch(SearchRequest request) { +// return remoteSearchService.searchFlights(request, remoteSearchService); +// } + + + @RateLimiter(name = "changeLimitsExample") + public List changeLimitsExample(SearchRequest request) { + return remoteSearchService.searchFlights(request); + } + + @Retry(name = "retryAndRateLimitExample") + @RateLimiter(name = "retryAndRateLimitExample") + public List retryAndRateLimit(SearchRequest request) { + return remoteSearchService.searchFlights(request); + } + + @RateLimiter(name = "rateLimiterEventsExample") + public List rateLimiterEventsExample(SearchRequest request) { + return remoteSearchService.searchFlights(request); + } + + public void updateRateLimits(String rateLimiterName, int newLimitForPeriod, Duration newTimeoutDuration) { + io.github.resilience4j.ratelimiter.RateLimiter limiter = registry.rateLimiter(rateLimiterName); + limiter.changeLimitForPeriod(newLimitForPeriod); + limiter.changeTimeoutDuration(newTimeoutDuration); + } + + @RateLimiter(name = "fallbackExample", fallbackMethod = "localCacheFlightSearch") + public List fallbackExample(SearchRequest request) { + return remoteSearchService.searchFlights(request); + } + + private List localCacheFlightSearch(SearchRequest request, RequestNotPermitted rnp) { + System.out.println("Returning search results from cache"); + return Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 781", request.getFlightDate(), request.getFrom(), request.getTo())); + } + + @PostConstruct + public void postConstruct() { + io.github.resilience4j.retry.Retry.EventPublisher retryEventPublisher = retryRegistry + .retry("retryAndRateLimitExample") + .getEventPublisher(); + + retryEventPublisher.onRetry(System.out::println); + retryEventPublisher.onSuccess(System.out::println); + + io.github.resilience4j.ratelimiter.RateLimiter.EventPublisher eventPublisher = registry + .rateLimiter("rateLimiterEventsExample") + .getEventPublisher(); + + eventPublisher.onSuccess(System.out::println); + eventPublisher.onFailure(System.out::println); + } +} + +@Component +class RPMRateLimitedFlightSearchSearch { + @RateLimiter(name = "multipleRateLimiters_rpm_limiter") + List searchFlights(SearchRequest request, FlightSearchService remoteSearchService) { + return remoteSearchService.searchFlights(request); + } +} \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java index f1561a2..b0faf50 100644 --- a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java @@ -11,6 +11,9 @@ public class SpringbootResilience4jApplication { @Autowired private RetryExamplesRunner retryExamplesRunner; + @Autowired + private RateLimiterExamplesRunner rateLimiterExamplesRunner; + public static void main(String[] args) { SpringApplication.run(SpringbootResilience4jApplication.class, args); } @@ -18,5 +21,6 @@ public class SpringbootResilience4jApplication { @EventListener(ApplicationReadyEvent.class) public void runExamples() { retryExamplesRunner.run(); + rateLimiterExamplesRunner.run(); } } \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/resources/application.yml b/resilience4j/springboot-resilience4j/src/main/resources/application.yml index 1ad9df7..729a76f 100644 --- a/resilience4j/springboot-resilience4j/src/main/resources/application.yml +++ b/resilience4j/springboot-resilience4j/src/main/resources/application.yml @@ -44,6 +44,61 @@ resilience4j: - java.lang.Exception waitDuration: 2s + # Retry object used in RateLimitingService.retryAndRateLimitExample() + retryAndRateLimitExample: + maxRetryAttempts: 2 + waitDuration: 1s + + ratelimiter: + instances: + + # RateLimiter object used in RateLimitingService.basicExample() + basicExample: + limitForPeriod: 1 + limitRefreshPeriod: 1s + timeoutDuration: 1s + + # RateLimiter object used in RateLimitingService.timeoutExample() + timeoutExample: + limitForPeriod: 1 + limitRefreshPeriod: 1s + timeoutDuration: 250ms + + # RateLimiter object used in RateLimitingService.multipleRateLimitsExample() + multipleRateLimiters_rps_limiter: + limitForPeriod: 2 + limitRefreshPeriod: 1s + timeoutDuration: 2s + + multipleRateLimiters_rpm_limiter: + limitForPeriod: 40 + limitRefreshPeriod: 1m + timeoutDuration: 2s + + # RateLimiter object used in RateLimitingService.changeLimitsExample() + changeLimitsExample: + limitForPeriod: 1 + limitRefreshPeriod: 1s + timeoutDuration: 1s + + # RateLimiter object used in RateLimitingService.retryAndRateLimitExample() + retryAndRateLimitExample: + limitForPeriod: 1 + limitRefreshPeriod: 1s + timeoutDuration: 250ms + + # RateLimiter object used in RateLimitingService.rateLimiterEventsExample() + rateLimiterEventsExample: + limitForPeriod: 1 + limitRefreshPeriod: 1s + timeoutDuration: 50ms + + # RateLimiter object used in RateLimitingService.fallbackExample() + fallbackExample: + limitForPeriod: 1 + limitRefreshPeriod: 1s + timeoutDuration: 500ms + management: endpoints: web: