API管理におけるRate Limiting

Qi Guo

Qi Guo

September 16, 2022

Technology

インターネットの発展に伴い、ますます多くの企業がクラウドネイティブとマイクロサービスを採用し始めています。しかし、クラウドネイティブとマイクロサービスの技術的特性により、数百の異なるサービスを同時に管理しなければなりません。そのため、システム全体の円滑な運用を考慮するだけでなく、各APIベースのサービスのセキュリティと安定性にも注意を払う必要があります。

レートリミットは、APIベースのサービスの安定性を確保するための最も重要なソリューションの一つです。しかし、各サービスがレートリミットを必要とする場合、アプリケーションは非常に肥大化してしまいます。デジタル世界のすべてのトラフィックの出入り口として、APIゲートウェイはすべてのサービスの統一されたAPI管理を実現し、システムサービスの安定した運用を保護します。この記事では、Apache APISIX APIゲートウェイを通じてレートリミットを実現する方法を示し、レートリミットの戦略と技術について説明します。

レートリミットとは

レートリミットは、インターネットトラフィックを制御し、スループットを最大化するための戦略です。レートリミットを使用すると、特定の制約下にあるリクエストのみがシステムにアクセスできるようになり、制約を超える追加のリクエストはキューに入れられ、優先度が下げられるか、拒否または破棄されます。レートリミットはまた、トラフィックスパイクや悪意のある攻撃などの予期せぬ事故からシステムを保護し、一貫した安定したサービスを提供できるようにします。

例えば、トレンドのツイートがトラフィックスパイクを引き起こす場合、Twitterはサーバーのクラッシュを防ぐためにレートリミットを設定する必要があります。

なぜ必要なのか

まず、私たちの日常生活でレートリミットを使用する簡単なユースケースを見てみましょう。例えば、観光地では休日のチケットを一定数しか販売できません。また、人気レストランでは事前に予約するか、長い待ち時間を経て食事を楽しむ必要があります。

APIゲートウェイにおいても、レートリミットには多くの利点があります。レートリミットはAPIベースのサービスにいくつかの制約を設け、その円滑な運用を確保し、トラフィックスパイクによるサーバークラッシュによる不必要な損失を回避します。ここでは、5つの異なる実用的な制約をリストアップしました:

  1. リクエストレートを制限する。
  2. 時間単位あたりのリクエスト数を制限する。
  3. リクエストを遅延させる。
  4. クライアントのリクエストを拒否する。
  5. レスポンスレートを制限する。

いつ必要になるのか

識別と認証と組み合わせることで、レートリミットはその利点を最大限に発揮し、システムの可用性を次のように向上させることができます:

  1. 悪意のある攻撃を回避する。
  2. システムの安定した運用を確保し、トラフィックスパイクによるサーバークラッシュを防ぐ。
  3. 上流または下流サービスのバグによって引き起こされるリクエストスパイクによるサーバークラッシュを防ぐ。
  4. 高価なAPI呼び出しが頻繁に行われるのを防ぐ。
  5. API呼び出しの頻度を制限することで、不必要なリソースの浪費を減らす。

レートリミットの背後にある理論

前のセクションでは、レートリミットの利点を紹介しました。このセクションでは、その背後にある理論を見つけましょう!レートリミットの低レベルの実装は、特定のアルゴリズムによって達成されます。一般的に使用されるものには以下があります:

  • カウンターアルゴリズム
    • 固定ウィンドウ
    • スライディングウィンドウ
  • リーキーバケットアルゴリズム
  • トークンバケットアルゴリズム

カウンターアルゴリズム

カウンターアルゴリズムは比較的理解しやすく、2つのタイプがあります:

最初のタイプは固定ウィンドウアルゴリズムで、固定された時間単位でカウンターを維持し、時間単位が経過したことを検出するとカウンターをゼロにリセットします。

2番目のタイプはスライディングウィンドウアルゴリズムで、最初のアルゴリズムに基づいて改善されています。以下の手順があります:

  1. 時間単位をいくつかの間隔(各ブロックと呼ばれる)に分割する。
  2. 各ブロックにはカウンターがあり、着信リクエストがあるとカウンターが1増加する。
  3. 一定時間が経過すると、この時間ウィンドウが1ブロック前に進む。
  4. 時間ウィンドウ内のすべてのブロックのカウンターを合計して、その時間ウィンドウ内の総リクエスト数を計算します。総リクエストが制約を超える場合、その時間ウィンドウ内のすべてのリクエストをドロップします。

リーキーバケットアルゴリズム

リーキーバケットがあるとします。すべてのリクエストは最初にキューに入れられ、その後リーキーバケットは一定のレートでそれらを送信します。

リーキーバケットアルゴリズム

リクエストがバケットの容量を超える場合、システムは溢れたリクエストを破棄し、拒否します。リーキーバケットアルゴリズムはリクエストレートを制限し、すべてのリクエストが一定のレートで送信されるようにします。これにより、簡単に入るが難しい出るモードが作成されます。

このアルゴリズムの核心ステップ:

  1. すべてのリクエストは固定サイズのバケットに保存されます。
  2. バケットは空になるまで一定のレートでリクエストを送信します。
  3. バケットが満杯になると、システムは追加のリクエストを破棄します。

トークンバケットアルゴリズム

トークンバケットアルゴリズムは、トークンの生成と破棄の2つの部分で構成されています。トークンバケットは一定のレートでトークンを生成し、固定されたストレージバケットに保存します。リクエストがトークンバケットを通過すると、リクエストは1つ以上のトークンを取得します。トークンバケット内のトークン数が最大容量に達すると、トークンバケットは新しく生成されたトークンを破棄します。また、ストレージバケットにトークンが残っていない場合、着信リクエストは拒否されます。

トークンバケットアルゴリズム

トークンバケットアルゴリズムの核心ステップ:

  1. トークンバケットは一定のレートでトークンを生成し、ストレージバケットに入れます。
  2. トークンバケットが満杯の場合、新しく生成されたトークンは直接破棄されます。リクエストが到着すると、ストレージバケットから1つ以上のトークンを取得します。
  3. トークンバケットにトークンが残っていない場合、システムは着信リクエストを拒否します。

APIゲートウェイによるレートリミットの実現

管理するAPIベースのサービスが少ない場合、サービス内で直接レートリミットアルゴリズムを使用することができます。例えば、Goを使用してシステムを開発している場合、tollboothgolang.org/x/time/rateを使用してアルゴリズムを実装できます。Luaを使用している場合、NGINXのlimit_reqlimit_conn、およびLua-resty-limit-trafficモジュールを使用してアルゴリズムを実装できます。

レートリミットがAPIベースのサービスに基づいて実装されている場合、レートリミットの制約はサービス自体によって設定され、各サービスは異なる制約を持つ可能性があります。APIベースのサービスの数が大幅に増加すると、制約と違いが管理レベルの問題を引き起こします。その時点で、APIゲートウェイを使用してすべてのAPIサービスを一元的に管理することはできません。APIゲートウェイを使用してレートリミットの問題を解決する際に、識別、認証、ログ可観測性などの無関係なビジネス機能をゲートウェイに実装することもできます。

Apache APISIXは、動的でリアルタイムの高性能なクラウドネイティブゲートウェイです。APISIXは現在80以上の異なるプラグインをサポートしており、豊富なエコシステムを構築しています。APISIXのプラグインを使用してAPIベースのサービスのトラフィックを管理できます。これには、limit-reqlimit-conn、およびlimit-countが含まれます。ここでは、APISIXのレートリミットプラグインの使用法を示すユースケースを共有します。

ユーザーログインを支援するAPIベースのサービス(/user/login)があるとします。悪意のある攻撃やリソースの枯渇を避けるために、システムの安定性を確保するためにレートリミット機能を有効にする必要があります。

リクエストの制限

limit-reqプラグインはリクエストレートを制限し、リーキーバケットアルゴリズムを使用します。対応するルートまたは特定の顧客にバインドされます。

APISIXのAdmin APIを使用してこのようなルートを作成できます:

X-API-KeyはAPISIX設定のadmin_keyです。

curl http://127.0.0.1:9080/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/user/login",
    "plugins": {
        "limit-req": {
            "rate": 3,
            "burst": 2,
            "rejected_code": 503,
            "key": "remote_addr"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'

このコードスニペットの意味:クライアントのIPアドレスをリクエストレートを制限する要件として使用します。

  • リクエストレートが3リクエスト/秒(rate)未満の場合、リクエストは正常です。
  • リクエストレートが3リクエスト/秒を超え、5リクエスト/秒(rate+burst)未満の場合、超過したリクエストは優先度が下げられます。
  • リクエストレートが5リクエスト/秒(rate+burst)を超える場合、最大制約を超えるリクエストはHTTPコード503を返します。

limit-reqの詳細については、このドキュメントを確認してください:APISIX limit-req

接続の制限

limit-connプラグインは並列リクエスト(または並列接続)を制限します。以下は/user/loginに対してこのプラグインを有効にするための例です:

curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "methods": ["POST"],
    "uri": "/user/login",
    "id": 1,
    "plugins": {
        "limit-conn": {
            "conn": 3,
            "burst": 2,
            "default_conn_delay": 0.1,
            "rejected_code": 503,
            "key": "remote_addr"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'

このコードスニペットの意味:クライアントのIPアドレスを並列リクエストを制限する要件として使用します。

  • 同じクライアントの並列接続が3(conn)未満の場合、正常なステータス200を返します。
  • 並列接続が3(conn)を超え、5(conn+burst)未満の場合、超過したリクエストは遅延され、0.1秒の遅延時間が増加します。
  • 並列接続が5(conn+burst)を超える場合、このリクエストは拒否され、HTTPコード503を返します。

limit-connの詳細については、このドキュメントを確認してください:APISIX limit-conn

カウントの制限

limit-countプラグインはGithubのAPIレートリミットに似ています。特定の時間間隔内の総リクエスト数を制限し、残りのリクエスト数をHTTPヘッダーに返します。以下は/user/loginに対してこのプラグインを有効にするための例です:

curl -i http://127.0.0.1:9080/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/user/login",
    "plugins": {
        "limit-count": {
            "count": 3,
            "time_window": 60,
            "rejected_code": 503,
            "key": "remote_addr",
            "policy": "local"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:9001": 1
        }
    }
}'

このコードスニペットの意味:クライアントのIPアドレスをリクエスト数を制限する要件として使用します。カウンターはメモリ内にローカルに保存されます。

60秒(time_window)以内に3回(count)を超えるリクエストがある場合、3回を超えるリクエストはHTTPコード503(rejected_code)を返します。

limit-countの詳細については、このドキュメントを確認してください:APISIX limit-count

Apache APISIXレートリミットの利点

NGINXを使用してトラフィックを管理する場合、APIリクエストの数がトラフィックのバーストを引き起こすと、NGINXはその欠点を露呈します。その欠点の一つは、設定を動的にロードできないことです。一方、APISIXのサービス(ルートやサービスなど)は設定のホットリロードをサポートしています。トラフィックのバーストが発生しても、APISIXはレートリミットやその他のセキュリティプラグインの設定を即座に変更できます。etcdのウォッチメカニズムのおかげで、APISIXはサービスをリロードすることなくミリ秒単位でデータ層を更新できます。

それに加えて、APISIXはクラスターレベルのレートリミットもサポートしています。例えば、limit-countを使用してpolicyの設定をredisまたはredis-clusterに変更できます。これにより、異なるAPISIXノード間で計算結果を共有することで、クラスターレベルのレートを制限できます。

DevOpsとして、すべてのAPIサービスを管理するためにグラフィカルなダッシュボードを使用すると、生産性が向上します。APISIXは、API設定の変更をより便利にするための整った視覚的な管理ダッシュボードを提供します。

結論

レートリミットは実際のビジネスシナリオで一般的なニーズであり、トラフィックスパイクからシステムを保護し、その円滑な運用を確保するための重要な方法です。レートリミットはAPIサービス管理の一部に過ぎません。他にも多くの技術を使用して、重要なセキュリティサポートを提供し、ユーザーエクスペリエンスを向上させることができます。

Tags: