Introduction
Rate limiting is technique to help to limit the number of requests or type of request received by a server. It help to scale and increase the reliability of the system. As per resilience4j doc
Rate limiting is an imperative technique to prepare your API for scale and establish high availability and reliability of your service. But also, this technique comes with a whole bunch of different options of how to handle a detected limits surplus, or what type of requests you want to limit. You can simply decline this over limit request, or build a queue to execute them later or combine these two approaches in some way.
Version Details
- spring-boot:2.2.6.RELEASE
- spring-cloud:Hoxton.SR4
- Resilience4j:1.1.0
- Java:11
- Kotlin:1.3.71
Dependencies
We need to add following dependencies in pom.xml
file -
- resilience4j-reactor
- resilience4j-circuitbreaker
- resilience4j-spring-boot2
- spring-boot-starter-actuator
- spring-boot-starter-aop
Configure rate limiter in the application.yml file
Open application.yml
and add the following configuration for the rate limiter
-
resilience4j.ratelimiter:
instances:
processService:
limitForPeriod: 10
limitRefreshPeriod: 1s
timeoutDuration: 0
The details of the configuration is as below -
Config property | Default value | Description |
---|---|---|
timeoutDuration | 5 [s] | The default wait time a thread waits for a permission |
limitRefreshPeriod | 500 [ns] | The period of a limit refresh. After each period the rate limiter sets its permissions count back to the limitForPeriod value |
limitForPeriod | 50 | The number of permissions available during one limit refresh period |
Add rate limiter to the service
I created a simple service that takes no arguments, and return some string mono. We shall add the @RateLimiter
annotation, and pass the config name and fallback method name, that would be call in case of request denied by the rate limiter.
@RateLimiter(name="processService", fallbackMethod = "processFallback")
fun process(): Mono<String> {
return Mono.just("Hello World ...")
}
The fallback method name is processFallback
it should be in the same class and it should have the same signature but with an extra parameter for the Throwable
class for the exception handling.
So fallback method should look like this
fun processFallback(exp: Throwable): Mono<String> {
log.error("eh!!! this is the error ${exp.localizedMessage}")
return Mono.just("inside from fallback method because `${exp.localizedMessage}`")
}
Create controller class
The controller class should accept a get request and return the response Mono -
@GetMapping("/process", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun processFallback(exp: Throwable): Mono<String> {
log.error("eh!!! this is the error ${exp.localizedMessage}")
return Mono.just("inside from fallback method because `${exp.localizedMessage}`")
}
Run Application
Before running the application check the status of the available permission for this service using actuator health endpoint -
We have only one permission, now run the following script on the terminal
http :8080/process
Output
$ http :8080/process
HTTP/1.1 200 OK
Content-Type: text/event-stream;charset=UTF-8
transfer-encoding: chunked
data:ah what do you want ...
Rate limiter permission details
Now this service have zero permission; now call it again and see, the call should be rejected now -
http :8080/process
Output
$ http :8080/process
HTTP/1.1 200 OK
Content-Type: text/event-stream;charset=UTF-8
transfer-encoding: chunked
data:inside from fallback method because `RateLimiter 'processService' does not permit further calls`
Source Code
The full source code is available at GitHub
Reference