guava の RateLimiter を用いた Rate-Limiting

https://github.com/google/guava

guava は google が OSS として提供しているもので、Java で必要な様々なコア処理をライブラリとして提供しています。
その中に Rate-Limiting 処理を行うための RateLimiter というクラスが含まれているため、簡単に使ってみます。

# Sample
- PERMITS_PER_SECONDS: 同時に処理できる処理件数
- PERMITS_CONSUMED: 一度の処理で消費する処理件数（一回の処理の重みを変えたい場合に、この値を変える）
- RateLimiter#tryAcquire: 渡した PERMITS_CONSUMED を消費できるかどうかを試すメソッド。二番目の引数はタイムアウト値。タイムアウトまでに取得ができれば true、できなければ false を返す。

という仕組みを用いて、Spring Boot の Controller で Rate-Limiting を実現しようとすると以下のようになります。

```
@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));
        }};
    }
}
```

上記のような形で、同時処理件数の排他制御を RateLimter を用いて行うことにより、簡易的な Rate-Limiting 処理を実装することができます。

# Running
## 通常時（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"}
```

## Rate-Limit 時（実装上では500が返る）
```
< 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
guava の RateLimiter を用いて上記のような Naive な Rete-Limiting 処理をは書くことができます。

ただ、 Rate-Limiting は実装や運用で配慮すべき事項も多く、自分でイチから実装するのはあまり現実的ではないかもしれません。

世の中の OSS で RateLimter を使用して Rate-Limiting を行っているいる Web Framework として Armeria がああります。

https://github.com/line/armeria

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

Armeria では Throttling (Rate-Limiting) の処理を Decorator で定義することができるようになっていて、導入もしやすそうです。こういった OSS を使用して Web Application を開発するのも一つの手だと思います。

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



なお、Armeria では bucket4j ベース (Token Bucket ベース)の Throttling にも対応しています。

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