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
< 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"}
< 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