私のサイトの正当なユーザーは、望ましくない結果を引き起こすAPIリクエストでサーバーを槌で打つことがあります。5秒ごとに1回のAPI呼び出し、または1分あたりn回の呼び出しという制限を設けたいと思います(正確な制限はまだわかりません)。明らかに、すべてのAPI呼び出しをDBに記録し、すべてのリクエストで計算を行って、制限を超えているかどうかを確認できますが、すべてのリクエストでのこの余分なオーバーヘッドはすべて、目的を達成できません。制限を設けるために使用できる、リソースをあまり消費しない他の方法は何ですか?私はPHP/Apache/Linuxを使用しています。
7 に答える
わかりました、サーバーへの書き込みなしで私が要求したことを行う方法はありませんが、少なくともすべての要求のログ記録をなくすことはできます。$last_api_request
1 つの方法は、「リーキー バケット」スロットリング メソッドを使用することです。この方法では、最後のリクエスト ( ) と、時間枠のリクエスト数/制限の比率( )のみを追跡します$minute_throttle
。n
リーキー バケットはカウンターをリセットしません (1 時間ごとにリセットされる Twitter API のスロットルとは異なります) が、バケットがいっぱいになる (ユーザーが制限に達した) 場合、別のリクエストを行う前に、バケットが空になるまで数秒待つ必要があります。つまり、ローリング リミットのようなものです。時間枠内に以前のリクエストがある場合、それらはバケットからゆっくりと漏れ出します。バケツをいっぱいにした場合にのみ制限されます。
このコード スニペットは、$minute_throttle
リクエストごとに新しい値を計算します。時間単位、日単位など、任意の期間のスロットルを追加できるため、分を指定しました...ただし、複数のスロットルがすぐにユーザーを混乱させ始めます.$minute_throttle
$minute = 60;
$minute_limit = 100; # users are limited to 100 requests/minute
$last_api_request = $this->get_last_api_request(); # get from the DB; in epoch seconds
$last_api_diff = time() - $last_api_request; # in seconds
$minute_throttle = $this->get_throttle_minute(); # get from the DB
if ( is_null( $minute_limit ) ) {
$new_minute_throttle = 0;
} else {
$new_minute_throttle = $minute_throttle - $last_api_diff;
$new_minute_throttle = $new_minute_throttle < 0 ? 0 : $new_minute_throttle;
$new_minute_throttle += $minute / $minute_limit;
$minute_hits_remaining = floor( ( $minute - $new_minute_throttle ) * $minute_limit / $minute );
# can output this value with the request if desired:
$minute_hits_remaining = $minute_hits_remaining >= 0 ? $minute_hits_remaining : 0;
}
if ( $new_minute_throttle > $minute ) {
$wait = ceil( $new_minute_throttle - $minute );
usleep( 250000 );
throw new My_Exception ( 'The one-minute API limit of ' . $minute_limit
. ' requests has been exceeded. Please wait ' . $wait . ' seconds before attempting again.' );
}
# Save the values back to the database.
$this->save_last_api_request( time() );
$this->save_throttle_minute( $new_minute_throttle );
トークン バケット アルゴリズムを使用してレートを制御できます。これは、リーキー バケット アルゴリズムに匹敵します。バケットの状態 (つまり、トークンの量) をプロセス (または制御したいスコープ) で共有する必要があることに注意してください。そのため、競合状態を回避するためにロックを検討することをお勧めします。
良いニュース: 私はあなたのためにそれをすべてやりました: bandwidth-throttle/token-bucket
use bandwidthThrottle\tokenBucket\Rate;
use bandwidthThrottle\tokenBucket\TokenBucket;
use bandwidthThrottle\tokenBucket\storage\FileStorage;
$storage = new FileStorage(__DIR__ . "/api.bucket");
$rate = new Rate(10, Rate::SECOND);
$bucket = new TokenBucket(10, $rate, $storage);
$bucket->bootstrap(10);
if (!$bucket->consume(1, $seconds)) {
http_response_code(429);
header(sprintf("Retry-After: %d", floor($seconds)));
exit();
}
このスレッドがまだ生きているかどうかはわかりませんが、これらの統計を memcached のようなメモリ キャッシュに保持することをお勧めします。これにより、リクエストをDBに記録するオーバーヘッドが削減されますが、それでも目的は果たされます。
最も簡単な解決策は、各 API キーに 24 時間あたりの限られた数のリクエストを与え、既知の固定された時間にリセットすることです。
それらが API リクエストを使い果たした場合 (つまり、カウントしている方向に応じてカウンターがゼロまたは制限に達した場合)、カウンターをリセットするまでデータの提供を停止します。
このようにして、あなたにリクエストをぶつけないことが彼らの最善の利益になります。
「すべてのリクエストで余分なオーバーヘッドが発生すると、目的が果たせなくなる」とおっしゃっていますが、それが正しいかどうかはわかりません。サーバーのハンマーを防ぐ目的ではありませんか?これはおそらく私がそれを実装する方法です。なぜなら、それは実際には迅速な読み取り/書き込みのみを必要とするからです。パフォーマンスが心配な場合は、APIサーバーのチェックを別のDB/ディスクにファームアウトすることもできます。
ただし、代替手段が必要な場合は、帯域幅調整を支援するように設計されたサードパーティのapacheモジュールであるmod_cbandを確認する必要があります。主に帯域幅を制限するためのものですが、1秒あたりの要求数に基づいて調整することもできます。使ったことがないので、どんな結果になるかわかりません。mod-throttleと呼ばれる別のモジュールもありましたが、そのプロジェクトは現在終了しているようで、Apache1.3シリーズ以降ではリリースされていません。
ゼロからの実装に加えて、レート制限を行う 3scale ( http://www.3scale.net ) のような API インフラストラクチャや、他の多くのもの (分析など) を調べることもできます。そのための PHP プラグインがあります: https://github.com/3scale/3scale_ws_api_for_php。
API の前に Varnish のようなものを貼り付けて、そのような API レート制限を行うこともできます。