Rate-Limiting using guava's RateLimiter

https://github.com/google/guava

guava is provided by google as OSS, and provides various core processes required by Java as a library. It contains a class called RateLimiter for performing Rate-Limiting processing, so let's use it easily.

Sample --PERMITS_PER_SECONDS: Number of processes that can be processed at the same time --PERMITS_CONSUMED: Number of processes consumed in one process (change this value if you want to change the weight of one process) --RateLimiter # tryAcquire: A method that tests whether the passed PERMITS_CONSUMED can be consumed. The second argument is the timeout value. Returns true if it can be obtained by the timeout, false otherwise.

If you try to realize Rate-Limiting with the Controller of Spring Boot using the mechanism, it will be as follows.

@Slf4j
@Controller
public class TestController {
    private static final Double PERMITS_PER_SECONDS = 1d;
    private static final int PERMITS_CONSUMED = 1;
 
    private AtomicInteger index = new AtomicInteger(0);
    private RateLimiter rateLimiter = RateLimiter.create(PERMITS_PER_SECONDS);
 
    @GetMapping("/hello")
    @ResponseBody
    public HashMap<String, String> sayHello() {
        final int id = index.incrementAndGet();
        final boolean acquired = rateLimiter.tryAcquire(PERMITS_CONSUMED, Duration.ofMillis(100L));
        if (!acquired) {
            throw new UncheckedIOException(new IOException("Rate Limit."));
        }
        log.debug("Serving request id " + id);
        return new HashMap<>() {{
            put("message", "Hello");
            put("id", String.valueOf(id));
            put("acquired", String.valueOf(acquired));
        }};
    }
}

A simple rate-limiting process can be implemented by using RateLimter to exclusively control the number of simultaneous processes in the above manner.

Running

Normal time (200)

< HTTP/1.1 200
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Mon, 20 Apr 2020 10:38:20 GMT
<
{ [60 bytes data]
 100    49    0    49    0     0  24500      0 --:--:-- --:--:-- --:--:-- 24500
* Connection #0 to host localhost left intact
{"id":"3092","message":"Hello","acquired":"true"}

At Rate-Limit (500 is returned on implementation)

< HTTP/1.1 500 
< Content-Type: application/json
< Transfer-Encoding: chunkedj
< Date: Mon, 20 Apr 2020 10:38:43 GMT
< Connection: close
< [
{ [5072 bytes data]
*100  5064    0  5064    0     0  2472k      0 --:--:-- --:--:-- --:--:-- 2472k
* Closing connection 0
{"timestamp":"2020-04-20T10:38:43.015+0000","status":500,"error":"Internal Server Error","message":"java.io.IOException: Rate Limit.","t
race":"java.io.UncheckedIOException: java.io.IOException: Rate Limit.  (snip.)

Appendix You can write the above Naive Rete-Limiting process using guava's RateLimiter.

However, Rate-Limiting has many things to consider in implementation and operation, so it may not be very realistic to implement it from scratch.

Armeria is a Web framework that uses RateLimter to perform rate-limiting on OSS in the world.

https://github.com/line/armeria

https://github.com/line/armeria/blob/f03b400cff136a710dbdbeb75e0e4f741387d296/core/src/main/java/com/linecorp/armeria/server/throttling/RateLimitingThrottlingStrategy.java

Armeria allows you to define Throttling (Rate-Limiting) processing with Decorator, which seems to be easy to introduce. I think one way is to develop a Web Application using such OSS.

https://image.slidesharecdn.com/2018-180227110721/95/building-asynchronous-microservices-with-armeria-24-638.jpg?cb=1519731604

Armeria also supports bucket4j-based (Token Bucket-based) Throttling.

https://github.com/line/armeria/blob/e8eecafb35bb7eb50f6cdd614b0b5d1faca8a308/bucket4j/src/main/java/com/linecorp/armeria/server/throttling/bucket4j/TokenBucketThrottlingStrategy.java

Recommended Posts

Rate-Limiting using guava's RateLimiter
Rate-Limiting using RateLimiter of Resilience4j