Golangはじめて物語(第11話: Gin＋time/rateでお手軽に流量制御を行う)

# はじめに
[以前書いたSpringBoot編](https://qiita.com/neruneruo/items/37f0a0181b291eb6ffbd)で触れたとおり、マイクロサービスなシステムを作っていく上で、RateLimitは重要な要素になってくる。
Golangでは標準ライブラリでRateLimitが実装できるものが容易されているので、今回はそれをGinに組み込んで、お手軽に流量制御をやってみよう。

なお、記事執筆時点でGolangのランタイムは1.13を使っている。

# RateLimitライブラリ
RateLimitを行うライブラリは、[公式ドキュメント](https://pkg.go.dev/golang.org/x/time/rate)を参考にしよう。
このライブラリは、[トークンバケットアルゴリズム](https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%83%90%E3%82%B1%E3%83%83%E3%83%88)を用いているので、まずはこの基本を押さえておこう。
time/rateには、Allow()/Reserve()/Wait()という3種類のトークンコンシューマーがあるが、Wait()してしまうとクライアントを待たせてしまうため、HTTPサーバとしての流量制御を実装する場合は、Allow()かReserve()を用いる。細かい制御をしないのであれば、Allow()が良いだろう。

# 基本のHTTPサーバ
まずは、time/rateを実装する前の基本形を作っておこう。

```golang:main.go
package main

import (
	"net/http"
	"github.com/gin-gonic/gin"
)

type body struct {
	Detail string `json:"detail"`
}

func example() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON(http.StatusOK, body{
			Detail: http.StatusText(http.StatusOK),
		})
	}
}

func initRouter() *gin.Engine {
	router := gin.Default()
	router.GET("/example", example())
	return router
}

func main() {
	router := initRouter()
	router.Run(":8080")
}
```

お手軽！このWebサーバを起動して```curl -i http://localhost:8080/example```すると、

```
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sat, 09 Apr 2022 09:34:49 GMT
Content-Length: 15

{"detail":"OK"}
```

という結果を得られる。

さて、ではここにRateLimiterを実装していこう。

# コードの修正
ソースコードは以下のように変更しよう。

```golang:main.go

package main

import (
	"net/http"
	"time"                     // ★追加

	"github.com/gin-gonic/gin"
	"golang.org/x/time/rate"   // ★追加
)

type body struct {
	Detail string `json:"detail"`
}

var (                                                                                  // ★追加
	interval = 2                                                                       // ★追加
	burst    = 2                                                                       // ★追加
	limit    = rate.NewLimiter(rate.Every(time.Duration(interval)*time.Second), burst) // ★追加
)                                                                                      // ★追加

func example() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON(http.StatusOK, body{
			Detail: http.StatusText(http.StatusOK),
		})
	}
}

func rateLimiter() gin.HandlerFunc {                                 // ★追加
	return func(c *gin.Context) {                                    // ★追加
		if limit.Allow() == false {                                  // ★追加
			c.JSON(http.StatusTooManyRequests, body{                 // ★追加
				Detail: http.StatusText(http.StatusTooManyRequests), // ★追加
			})                                                       // ★追加
			c.Abort()                                                // ★追加
		}                                                            // ★追加
	}                                                                // ★追加
}                                                                    // ★追加

func initRouter() *gin.Engine {
	router := gin.Default()
	router.Use(rateLimiter()) // ★追加
	router.GET("/example", example())
	return router
}

func main() {
	router := initRouter()
	router.Run(":8080")
}
```

ポイントになるのは、```router.Use(rateLimiter())```でミドルウェアとして実装するところだ。
Ginのミドルウェアについては、[こちらの記事](https://qiita.com/tobita0000/items/d2309cc3f0dd32006ead)が詳しいので参照していただければ。
実際にプロダクションコードを作る場合、ヘルスチェックのパスがRateLimitの対象になっては困るので、

```golang
    router.GET("/example", rateLimiter(), example())
```

と実装したり、上記記事のGroupを用いるのが良いだろう。

あとは、

```golang
    limit = rate.NewLimiter(rate.Every(time.Duration(interval)*time.Second), burst)
```

で初期化したRateLimiterを、```limit.Allow()```で呼んで判定してあげれば良い。

なお、NewLimiterの引数は、第一引数が「トークンを補給するレート」だ。実際にはトークンバケットアルゴリズムにおける1/rのrを渡すことになる。
分かりにくいので、```rate.Every()```を使うことで、rを直接渡すことが可能だ。
今回のコードの例では、```interval```が2なので、2秒に1トークンが補給されるが、```burst```である2を最大として補給するという意味になる。(つまり、最大までトークンを補給するには4秒かかる)

# 動かしてみる
このコードでWebサーバを起動して、同様にcurlで投げ込んでみると、以下のようなログを出力する。

```
[GIN] 2022/04/09 - 18:50:45 | 200 |        69.4µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:45 | 200 |        33.6µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:45 | 429 |        36.5µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |          35µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |        37.7µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |        27.5µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |        21.5µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |        22.3µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |        21.9µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:46 | 429 |        41.3µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:47 | 429 |        22.1µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:47 | 429 |        22.7µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:47 | 429 |        21.2µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:47 | 200 |        57.7µs |       127.0.0.1 | GET      "/example"
[GIN] 2022/04/09 - 18:50:47 | 429 |        26.4µs |       127.0.0.1 | GET      "/example"
```

設定した通り、最初の2トランザクションでトークンを使い果たし、2秒後に1件補給されるが、1件正常応答後は補給町の状態になって"Too Many Requests"を応答する。

```rate.NewLimiter()```に渡すパラメータを制御することで、柔軟な流量制御が可能になる。