Rate Limitingライブラリ
このライブラリは、効率的でスケーラブル、かつ最終的に一貫性のあるスライディングウィンドウ流量制限ライブラリを提供するように設計されています。共有ngxメモリゾーンのアトミック操作を利用して、特定のノード内のウィンドウカウンターを追跡し、このデータを中央のデータストアに定期的に同期します。
スライディングウィンドウの流量制限の実装は、特定の時間枠内の特定のキー(IPアドレス、コンシューマ、認証情報など)に割り当てられたヒット数を追跡し、過去のヒット率を考慮して計算レートを平滑化します。同時に、現代の開発者が慣れ親しんでいる使い慣れたウィンドウインターフェイス(たとえば、ヒット数n/毎秒/毎分/時間)も提供します。これは、ウィンドウの開始時に要求レートがリセットされる固定ウィンドウの実装に似ていますが、固定ウィンドウの実装で発生する「リセットバンプ」がなく、リーキーバケットまたはトークンバケットの実装が提供できるものよりも直感的なインターフェイスを提供します。
注: このライブラリは抽象的な流量制限インターフェースを提供します。そのため、流量制限キーの増分値に言及する場合、「request」の代わりに「hit」という用語が使用されます。スライディングウィンドウの実装にはHTTPリクエストの流量制限以外の用途もあるため、「hit」は他のシナリオにも適用される広義の用語となります。
スライディングウィンドウでは、特定のキーの現在のレートを計算するときに、前のウィンドウの加重値が考慮されます。ウィンドウは、特定の「フロア」タイムスタンプから始まる期間として定義され、フロアはウィンドウのサイズに基づいて計算されます。ウィンドウサイズが60秒の場合、フロアは常に0秒目(たとえば、任意の時間(分)の開始時)に下がります。同様に、サイズが30秒のウィンドウは、毎分の0秒および30秒に開始されます。
1分あたり10ヒットの流量制限を考えてみます。この構成では、このライブラリは、現在のウィンドウ(現在の1分の初めから)のヒット数と、前のウィンドウ(たとえば、前の1分間)の全ヒット数の加重パーセンテージに基づいて、特定のキーのヒット率を計算します。この重みは、問題のウィンドウサイズに関する現在のタイムスタンプに基づいて計算されます。現在の時刻が前のウィンドウの先頭から遠ければ遠いほど、重みの割合は低くなります。この値は、次の例で表すのが最適です。
current window rate: 10
previous window rate: 40
window size: 60
window position: 30 (seconds past the start of the current window)
weight = .5 (60 second window size - 30 seconds past the window start)
rate = 'current rate' + 'previous weight' * 'weight'
= 10 + 40 * ('window size' - 'window position') / 'window size'
= 10 + 40 * (60 - 30) / 60
= 10 + 40 * .5
= 30
厳密に言えば、加重パーセンテージを定義するために使用される式は次のとおりです。
weight = (window_size - (time() % window_size)) / window_size
上記で、time()
は現在のUnixタイムスタンプの値です。
Kongクラスタの各ノードは、流量制限カウンターの信頼できる情報源として、独自のメモリ内データストアに依存しています。各ノードは定期的に、確認したキーごとにカウンター増分をクラスタにプッシュします。クラスタは、この差分を適切なキーにアトミックに適用することが期待されます。次に、ノードはこのキーの値を、このデータ同期サイクルに関連する他のキーとともにデータストアから取得します。このように、各ノードはデータの関連部分をクラスタと共有しますが、各リクエストの間、非常に高性能な方法でデータを追跡します。このクラスタ内のノード間の収束-> 分岐 -> 再収束のサイクルは、最終的に一貫性のあるモデルを提供します。
ノードが収束する周期は設定可能です。同期間隔を短くすると、トラフィックがクラスタ内の複数のノードに分散している場合(ラウンドロビンバランサーの背後にある場合など)、データポイントの分岐が少なくなります。一方、同期間隔が長いと、データストアへのR/W圧力が少なくなり、差分を計算して新しい同期値を取得するための各ノードのオーバーヘッドが少なくなります。ここでの望ましい値はユースケースによって異なります。クラスタ同期を使用してノードを定期的に更新する場合(たとえば、新しいクラスタノードにカウンターデータを通知する場合)、データストアのトラフィックを最小限に抑えるために、10〜30秒の値が望ましい場合があります。逆に、ノード間のより強い一貫性が要求される環境(クラスタメンバーシップ間のチャーン率が高い組織的なデプロイメントや、非ハッシュロードバランサーの背後にあるノードに厳格な流量制限ポリシーを適用する必要がある場合など)は、同期期間をミリ秒単位で短くする必要があります。最小値は0.001(1ミリ秒)ですが、実際にはこの値は、Kongノードと構成されたデータストア間のネットワークパフォーマンスによって制限されます。
このライブラリは、定期的なデータ同期動作に加えて、sync_rate
を 0
と定義することで、流量制限カウンターを同期パターンで実装できます。このような場合、指定されたカウンターはデータストアに直接適用されます。この動作は、クラスタ間でより強い一貫性が必要な場合に望ましいものです。このような構成では、リクエストごとにデータストア(またはRedis)と通信する必要があるというコストが伴い、リクエストに顕著な遅延が発生する可能性があります(「顕著」とは、対象となるストレージメカニズムのパフォーマンスに応じて、通常は数ミリ秒の相対的な用語です。比較すると、Kongは通常、数十マイクロ秒のオーダーでリクエストを処理します)。
このライブラリでは、0
未満の sync_rate
値を定義することで、カウンタデータの同期を完全に省き、ローカルメモリゾーンのみにインクリメンタルカウンターを適用することもできます。この動作は、Kongノードが1つしかない環境や、Kongノードがハッシュロードバランサーの背後にあり、独立したインスタンスとして扱われている環境など、クラスタ全体でデータを同期する必要がない場合に役立ちます。
同期レート、共有辞書名、ストレージポリシーなどのモジュール構成データは、ワーカーごとの公開構成テーブルに保存されます。複数の構成を任意のnamespaces
として保存するように定義できます(詳細は後述)。
開発者ノート
パブリック関数
このライブラリでは、以下のパブリック関数が提供されています。
ratelimiting.new_instance
構文:ratelimiting = ratelimiting.new_instance(instance_name)
以前は、このライブラリはモジュールレベルのグローバルテーブルのconfig
を使用していましたが、これにはプラグイン間で必要なデータ分離がありませんでした。2つ以上の異なるプラグインが同時にライブラリを使用する場合、Kong Gatewayは、どの名前空間がどのプラグインに属しているかを区別できませんでした。reconfigure
イベントが発生すると、プラグインは使用しなくなった名前空間をすべて削除しましたが、これらの削除された名前空間は他のプラグインに属していた可能性があります。
ratelimiting.new_instance
インターフェースは、元のインターフェースを変更せずに必要な分離を提供します。返されるインスタンスにはそれぞれ独自のratelimiting.config
があり、それらは互いに干渉しません。次の使用例を参照してください。 local ratelimiting = require("kong.tools.public.rate-limiting").new_instance("rate-limiting-foo")
ライブラリを古い方法で使用すると、動作は以前と同じです。この場合、他のプラグインと共有できる以下のようなデフォルトのインスタンスが返されます。local ratelimiting = require("kong.tools.public.rate-limiting")
ratelimiting.new
構文:ok = ratelimiting.new(opts)
新しい名前空間の構成を定義します。次のオプションが受け入れられます。
-
dict
: 使用する共有辞書の名前 -
sync_rate
: データの差分をストレージサーバーに同期する速度(秒単位)。-
strategy
: 使用するストレージストラテジ。PostgresSQL、Redisがサポートされています。ストラテジでは、以下に定義されるいくつかのパブリック関数を提供する必要があります。*strategy_opts
: ストレージストラテジで使用されるオプションのテーブル。現在は、「redis」ストラテジにのみ適用されます。
-
-
namespace
: これらの設定値を定義する文字列。名前空間は一度しか定義できません。名前空間がこのワーカーですでに定義されている場合は、エラーが表示されます。名前空間が定義されていない場合は、リテラル文字列「default」が使用されます。 -
window_sizes
: この構成で使用されるウィンドウサイズの一覧。
ratelimiting.increment
_syntax: rate = ratelimiting.increment(key, window_size, value, namespace?)_
指定された window_size
のキーを値でインクリメントします。名前空間が定義されていない場合は、「default」の名前空間が使用されます。値にはLuaタイプの任意の数値を使用できます(ただし、整数以外の値が提供されている場合は、この名前空間で使用されているストレージストラテジが10進値をサポートできることを確認してください)。この関数は、値の増分が適用された後のこのキーまたはwindow_size
のスライディングレートを返します。
ratelimit.sliding_window
_syntax: rate = ratelimit.sliding_window(key, window_size, cur_diff?, namespace?)_
このキー/window_size の現在のスライディングレートを返します。オプションのcur_diff
値を指定すると、このキーの現在保存されている差分を上書きできます。namespace
が定義されていない場合は、” のデフォルトの “ 名前空間が使用されます。
ratelimiting.sync
_syntax: ratelimiting.sync(premature, namespace?)_
このワーカーに現在保存されているすべてのキーの差分をストレージサーバーと同期し、新しく同期された値を取得します。名前空間が定義されていない場合は、”default” のnamespace
が使用されます。差分がプッシュされる前に、指定された名前空間の別の同期呼び出しがsync_rate
秒後に予定されています。このため、この関数は通常、init_worker
フェーズで呼び出して、再発生タイマーを初期化する必要があります。この関数はngx.timer
コンテキストで呼び出されることを意図しているため、最初の変数は注入されたpremature
パラメータを表します。
ratelimiting.fetch
_syntax: ratelimiting.fetch(premature, namespace, time, timeout?)_
指定された時間に指定された名前空間のすべての関連カウンターを取得します。この関数は、shm mutexを確立し、1回の実行ごとに単一のワーカーだけがshmを取得し設定するようにします。タイムアウトが定義されている場合、mutexは指定されたタイムアウト値に基づいて期限切れになります。それ以外の場合、mutexはディクショナリの更新後すぐにロック解除されます。この関数はngx.timer
コンテキストで呼び出すことができるため、最初の変数は注入されたpremature
パラメータを表します。
ストラテジ機能
ストレージストラテジでは、次のインターフェースを提供する必要があります。
strategy_class.new
_syntax: strategy = strategy_class.new(dao_factory, opts)_
新しいストラテジオブジェクトを実装してください。opts
はテーブル型であることが想定されており、ストラテジクラスに不透明または任意のオプションを渡すために使用できます。
strategy:push_diffs
_syntax: strategy:push_diffs(diffs)_
キー差分のテーブルをストレージサーバーにプッシュします。差分は、次の形式で提供されるテーブルになります。
[1] = {
key = "1.2.3.4",
windows = {
{
window = 12345610,
size = 60,
diff = 5,
namespace = foo,
},
{
window = 12345670,
size = 60,
diff = 5,
namespace = foo,
},
}
},
...
["1.2.3.4"] = 1,
...
strategy:get_counters
_syntax: rows = strategy:get_counters(namespace, window_sizes, time?)_
指定された namespace
とウィンドウサイズのリストについて、データストア/redisに保存されている各キーのイテレータを返します。「time」はオプションのUNIX秒精度タイムスタンプです。指定がない場合、この値はngx.time()
を介して設定されます。コンテキストに応じて、以前に定義されたタイムスタンプを介してこれを渡すことをお勧めします(たとえば、同じスレッド内で過去の呼び出しの実行に相当の時間がかかった場合など)。
strategy:get_window
_syntax: window = strategy:get_window(key, namespace, window_start, window_size)_
提供された値に基づいて、データストアから単一のキーを取得します。