レートリミット
概要
レートリミットは、任意の処理に対して実行回数の制限を適用します。 レートリミットが利用される典型的な例は、APIのリクエスト数の制御です。 APIのレートリミットとして最も一般的に用いられるのはTokenBucketアルゴリズムによるレートリミットです。
レートリミットはztime/zrateパッケージにより提供されています。
機能
1. LeakyBucket
本機能はLeakyBucketアルゴリズムによるレートリミットを実現します。
LeakyBucketのリミッター作成時に指定できるパラメータは以下の通りです。
queueSize
: キューサイズinterval
: リクエストを処理する間隔(デキューの間隔)
アルゴリズムの概要は以下になります。
- サイズ
N
のキューを作成する。- キューはFIFOで処理される。
- リクエストはキューに挿入される。
- キューが満杯の場合、リクエストを拒否する。
- 一定のインターバルでキューの先頭からリクエストを取り出し処理する。
LeakyBucketは以下の2つのメソッドを持ちます。
AllowNow
はキューが空の場合にのみ、有効なトークンを返す。
従って、LeakyBucketを利用する場合は基本的にはWaitNow
を利用し、リクエストが処理されるのを待機する。
func AllowNow() Token
func WaitNow(ctx context.Context) Token
APIのレートリミットに使用する例は、LeakyBucketによるAPIレートリミットを参照してください。
2. MaxConcurrency
本機能はMaxConcurrencyアルゴリズムによるレートリミットを実現します。
Concurrencyのリミッター作成時に指定できるパラメータは以下の通り。
concurrency
: 最大同時実行数
アルゴリズムの概要は以下になります。
N
個のセマフォ変数を作成する。- リクエストは1つのセマフォ変数をロックし、処理を開始する。
- セマフォ変数が全てロックされている場合、直ちに処理をエラーとするか、ロックを獲得できるまで待機する。
- 処理が完了するとセマフォ変数のロックを開放する。
Concurrencyリミッターは以下の2つのメソッドを持ちます。
AllowNow
はセマフォ変数が全てロックされている場合は直ちに無効なトークンを返却、一方WaitNow
はセマフォ変数のロックを獲得できるまで待機する。
func AllowNow() Token
func WaitNow(ctx context.Context) Token
APIのレートリミットに使用する例は、ConcurrencyによるAPIレートリミットを参照してください。
3. TokenBucket
本機能はTokenBucketアルゴリズムによるレートリミットを実現します。 TokenBucketはAPIのレートリミットにおいて広く用いられているアルゴリズムです。 TokenBucketアルゴリズムはバースト(瞬間的な処理数の増加)を許容するアルゴリズムです。
TokenBucketのリミッター作成時に指定できるパラメータは以下の通りです。
bucketSize
: バケットサイズfillRate
: トークン補充レート(基本的には1秒間割合を指す)
アルゴリズムの概要は以下になります。
- サイズ
N
のトークン用バケットを作成する。- バケットには一定間隔で
r
個のバケットが補充される。 - バケットが満杯であれば、それ以上トークンは補充されない。
- バケットには一定間隔で
- リクエストはバケットから1つのトークンを消費して処理を開始する。
- トークンが存在しなければ処理を開始できない。トークンの補充を待機するか直ちに処理を中断する。
TokenBucketリミッターは以下の2つのメソッドを持ちます。
AllowNow
は1つのトークンを消費して処理を開始します。トークンが存在しなければ直ちに無効なトークンが返却されるため、処理は中断されます。
一方、WaitNow
は同様にトークンを消費して処理を開始しますが、トークンが存在しなければ補充されるまで待機します。
func AllowNow() Token
func WaitNow(ctx context.Context) Token
APIのレートリミットに使用する例は、TokenBucketによるAPIレートリミットを参照してください。
4. FixedWindow
本機能はFixedWindowアルゴリズムによるレートリミットを実現します。 FixedWindowは一定区間における処理数を厳密に制限できる一方、異なる区間の境界において制限値の2倍の処理が実行される可能性のあるアルゴリズムです。
FixedWindowのリミッター作成時に指定できるパラメータは以下の通りです。
limit
: 処理数上限
アルゴリズムの概要は以下になります。
- サイズ
N
のトークン用バケットを作成する。- バケットには一定間隔で満杯になるようにトークンが補充される。
- リクエストはバケットから1つのトークンを消費して処理を開始する。
- トークンが存在しなければ処理を開始できない。トークンの補充を待機するか直ちに処理を中断する。
FixedWindowリミッターは以下の2つのメソッドを持ちます。
AllowNow
は1つのトークンを消費して処理を開始します。トークンが存在しなければ直ちに無効なトークンが返却されるため、処理は中断されます。
一方、WaitNow
は同様にトークンを消費して処理を開始しますが、トークンが存在しなければ補充されるまで待機します。
func AllowNow() Token
func WaitNow(ctx context.Context) Token
APIのレートリミットに使用する例は、FixedWindowによるAPIレートリミットを参照してください。
セキュリティに関する特記事項
セキュリティに関する特記事項はありません。
性能に関する特記事項
性能に関する特記事項はありません。
実装例・使い方
LeakyBucketによるAPIレートリミット
以下の例は、LeakyBucketアルゴリズムによるAPIレートリミットの簡単な実装例です。 必要に応じてパス単位などでレートリミットを適用することも可能です。
以下の例では、キューサイズが10、リクエストを処理するインターバルは100ミリ秒に設定されています。 また、リクエストが受理された場合、0-100ミリ秒間の間でランダムに待機した後200ステータスを返却します。
package main
import (
"log"
"math/rand/v2"
"net/http"
"time"
"github.com/aileron-projects/go/ztime/zrate"
)
func main() {
queueSize := 10
interval := time.Millisecond
limiter := zrate.NewLeakyBucketLimiter(queueSize, interval)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := limiter.WaitNow(r.Context())
defer token.Release()
if token.OK() {
time.Sleep(time.Duration(rand.Int64N(100)) * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
} else {
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("too many requests"))
}
})
log.Println("server listens on localhost:8080")
svr := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
}
if err := svr.ListenAndServe(); err != nil {
panic(err)
}
}
ConcurrencyによるAPIレートリミット
ConcurrencyアルゴリズムによるAPIレートリミットの簡単な実装例です。 必要に応じてパス単位などでレートリミットを適用することも可能です。
以下の例では、同時処理数が10に設定されています。 また、リクエストが受理された場合、0-100ミリ秒間の間でランダムに待機した後200ステータスを返却します。
package main
import (
"log"
"math/rand/v2"
"net/http"
"time"
"github.com/aileron-projects/go/ztime/zrate"
)
func main() {
concurrency := 10
limiter := zrate.NewConcurrentLimiter(concurrency)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := limiter.AllowNow()
defer token.Release()
if token.OK() {
time.Sleep(time.Duration(rand.Int64N(100)) * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
} else {
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("too many requests"))
}
})
log.Println("server listens on localhost:8080")
svr := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
}
if err := svr.ListenAndServe(); err != nil {
panic(err)
}
}
TokenBucketによるAPIレートリミット
TokenBucketアルゴリズムによるAPIレートリミットの簡単な実装例です。 必要に応じてパス単位などでレートリミットを適用することも可能です。
以下の例では、バケットサイズが10、トークン補充割合が10/秒に設定されています。 また、リクエストが受理された場合、0-100ミリ秒間の間でランダムに待機した後200ステータスを返却します。
package main
import (
"log"
"math/rand/v2"
"net/http"
"time"
"github.com/aileron-projects/go/ztime/zrate"
)
func main() {
bucketSize := 10
fillRate := 10
limiter := zrate.NewTokenBucketLimiter(bucketSize, fillRate)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := limiter.AllowNow()
defer token.Release()
if token.OK() {
time.Sleep(time.Duration(rand.Int64N(100)) * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
} else {
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("too many requests"))
}
})
log.Println("server listens on localhost:8080")
svr := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
}
if err := svr.ListenAndServe(); err != nil {
panic(err)
}
}
FixedWindowによるAPIレートリミット
FixedWindowアルゴリズムによるAPIレートリミットの簡単な実装例です。 必要に応じてパス単位などでレートリミットを適用することも可能です。
以下の例では、上限が10リクエスト/秒に設定されています。 また、リクエストが受理された場合、0-100ミリ秒間の間でランダムに待機した後200ステータスを返却します。
package main
import (
"log"
"math/rand/v2"
"net/http"
"time"
"github.com/aileron-projects/go/ztime/zrate"
)
func main() {
limit := 10
limiter := zrate.NewFixedWindowLimiter(limit)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := limiter.AllowNow()
defer token.Release()
if token.OK() {
time.Sleep(time.Duration(rand.Int64N(100)) * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
} else {
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("too many requests"))
}
})
log.Println("server listens on localhost:8080")
svr := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
}
if err := svr.ListenAndServe(); err != nil {
panic(err)
}
}
SlidingWindowによるAPIレートリミット
SlidingWindowアルゴリズムによるAPIレートリミットの簡単な実装例です。 必要に応じてパス単位などでレートリミットを適用することも可能です。
以下の例では、上限が10に設定されています。 また、リクエストが受理された場合、0-100ミリ秒間の間でランダムに待機した後200ステータスを返却します。
package main
import (
"log"
"math/rand/v2"
"net/http"
"time"
"github.com/aileron-projects/go/ztime/zrate"
)
func main() {
limit := 10
limiter := zrate.NewSlidingWindowLimiter(limit)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := limiter.AllowNow()
defer token.Release()
if token.OK() {
time.Sleep(time.Duration(rand.Int64N(100)) * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
} else {
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte("too many requests"))
}
})
log.Println("server listens on localhost:8080")
svr := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
}
if err := svr.ListenAndServe(); err != nil {
panic(err)
}
}
参考資料
フィードバック
このページは役に立ちましたか?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.