PHPを使って外部サイトからデータを引っぱってくるようなWebページを作っていると、外部サイトへのリクエスト送出を一定時間抑止したいことがあります。リクエスト過多で相手サーバに負荷をかけすぎないようにとの配慮ですが、AmazonのWeb APIなどでは「1秒間に複数のリクエスト禁止」というルールが決められていたりもしますね。
「ウチのサイトは訪問者も少ないし...」と思ってしまいがちですが、理論的には2人以上が同時にアクセスしてきていれば「1秒間に複数のリクエスト」が発生する可能性があります。クソ真面目にこのルールに従うべく、方法を考えてみましょう。
ただ単に「各訪問者の処理に1秒ずつ遅延をかける」というのは無意味。同時リクエストが送信されるタイミングが後ろに1秒ズレるだけです。自分のWebサーバ上にひとつ時計を用意して、それを各訪問者が参照し、複数ユーザのリクエストタイミングが重ならないようにしなければなりません。
ということで、ファイルの更新時刻を「時計」にして、1秒間に複数リクエストが送信されないようなしくみを作ってみた。意図したことができるだろうか。[2010.03.15追記]意図したことはできません! 解説と訂正版はこちらの記事を参照してください。ここから下は参考にする価値なし!です。
function sleeptimer( $file , $sec , $timeout){
$start = time();
while(1){
if( file_exists( $file )){
if( time()-filemtime( $file) > $sec ){ //最低$sec秒あいだを空ける
touch( $file ); //ファイルの更新時刻を更新
return true; //成功
}else{
if( time() - $start >= $timeout ){ //giveup
return false;
}
usleep( mt_rand(200000 , 800000 ) ); //0.2〜0.8秒待ちリトライ。
}
}else{
touch( $file );
return true;
}
}
}
?>
?>
$fileが「時計」として使うファイル名。ファイルパスもファイル名もなんでも良いですが、読み書き可能な場所に置かないと動きません。 $secは、待たせる秒数。ファイルの更新時刻は秒単位でしか得られないので、0.5秒待ちたい! とかはこの方法ではムリ。
運が悪いと延々と待たされることになってしまうので、$timeout秒待ってループを抜けられなかったらギブアップします。
この関数を、処理の時間間隔を調整したいところで呼び出せばOK。
$file = './tmp/timer';
$sec = 1;
$timeout = 10;
〜なんか処理〜
$flag = sleeptimer( $file , $sec , $timeout);
if( !$flag ) {
sendRequest( hoge ); //リクエストを送信。
}else{
〜なんか処理〜
}
?>
待たせているあいだ、ブラウザがフリーズしてしまったかのように見せない為の工夫などは、また別の話です。
コメント