web-dev-qa-db-ja.com

サイトのAPIユーザーを調整するにはどうすればよいですか?

私のサイトの正規のユーザーは、望ましくない結果を引き起こすAPIリクエストでサーバーをたたくことがあります。 5秒ごとに1回のAPI呼び出し、または1分あたりn回の呼び出しという制限を設けたいと思っています(正確な制限はまだわかりません)。私は明らかにすべてのAPI呼び出しをDBに記録し、すべてのリクエストで計算を行って制限を超えていないかどうかを確認できますが、すべてのリクエストに対するこの余分なオーバーヘッドはすべて目的を達成できません。制限を設けるために使用できる他のリソース集約型でない方法は何ですか? PHP/Apache/Linuxを使用しています。

50
scotts

わかりました。サーバーにany書き込みせずに、私が要求したことを実行する方法はありませんが、少なくともすべての要求をログに記録することを排除できます。 1つの方法は、「リーキーバケット」スロットリングメソッドを使用する方法です。この方法では、最後のリクエスト($last_api_request)と時間枠のリクエスト数/制限の比率($minute_throttle)のみを追跡します。 。リークのあるバケットは(1時間ごとにリセットされるTwitter APIのスロットルとは異なり)カウンタをリセットしませんが、バケットがいっぱいになると(ユーザーが制限に達した場合)、バケットが少し空になるまでn秒待機する必要があります彼らは別の要求をすることができます。つまり、ローリングリミットのようなものです。時間枠内に以前のリクエストがある場合、それらはバケットからゆっくりとリークしています。バケツをいっぱいにした場合にのみ制限されます。

このコードスニペットは、リクエストごとに新しい$minute_throttle値を計算します。 $minute_throttleを指定しました。毎時、毎日など、任意の期間にスロットルを追加できるためです。ただし、1つを超えるとすぐに混乱を招き、ユーザー。

$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 );
52
scotts

レートは トークンバケットアルゴリズム で制御できます。これは、リーキーバケットアルゴリズムに相当します。バケットの状態(つまり、トークンの量)をプロセス(または制御したいスコープ)で共有する必要があることに注意してください。したがって、競合状態を回避するためにロックについて考える必要があるかもしれません。

良いニュース:私はあなたのためにそれをすべて行いました: 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();
}
8
Markus Malkusch

このスレッドがまだ生きているかどうかはわかりませんが、これらの統計をmemcachedのようなメモリキャッシュに保持することをお勧めします。これにより、リクエストをDBに記録するオーバーヘッドが削減されますが、目的は満たされます。

4
Kedar Joshi

最も簡単な解決策は、各APIキーに24時間あたりのリクエスト数を制限し、既知の固定時間にそれらをリセットすることです。

APIリクエストを使い果たした場合(つまり、カウントする方向に応じてカウンターがゼロまたは制限に達した場合)、カウンターをリセットするまでデータの提供を停止します。

このように、それはtheirになるでしょう。

一からの実装に加えて、3scale( http://www.3scale.net )のようなAPIインフラストラクチャを確認することもできます。等。)。 PHPプラグイン: https://github.com/3scale/3scale_ws_api_for_php があります。

APIの前にVarnishのようなものを貼り付け、そのようにAPIレート制限を行うこともできます。

1
steve

あなたは「すべての要求に対するすべての余分なオーバーヘッドは目的を無効にするだろう」と言っていますが、それが正しいかどうかはわかりません。サーバーのハンマリングを防止する目的ではないですか?これは、実際には迅速な読み取り/書き込みのみを必要とするため、おそらく私が実装する方法です。パフォーマンスが心配な場合は、APIサーバーチェックを別のDB /ディスクにファームアウトすることもできます。

ただし、代替手段が必要な場合は、帯域幅スロットルを支援するように設計されたサードパーティのApacheモジュールである mod_cband を確認してください。主に帯域幅制限を目的としていますが、1秒あたりのリクエスト数に基づいてスロットルすることもできます。使用したことがないので、どのような結果が得られるかわかりません。 mod-throttleと呼ばれる別のモジュールもありましたが、そのプロジェクトは現在閉じられているようで、Apache 1.3シリーズ以上のものにはリリースされていません。

1
zombat