4

このコードはPHPでテキストファイルにビューを書き込み、数を増やしています。私の質問はここにあります:2人以上の人が私のサイトを使用してPHPスクリプトを実行している場合はどうなりますか?サーバーはそれを処理しますか?増加はファイルに保存されますか?

これが私のコードです(それが役立つ場合):

<?php

    $clicks = file_get_contents("clicks.txt");
    $clicks++;

        $fp = fopen("clicks.txt", "w+");
        fwrite($fp, $clicks);
        fclose($fp);

//give the count to the user
echo "result: $clicks";

    ?>
4

8 に答える 8

6

まず第一に:これは私がクリックカウンターを書く方法ではありません。

とは言うものの、100人のユーザーが同時に(最初のクリックが0で)サーバーにアクセスすると、記録された数は1..100になり、低い(=間違った)値が目立つようになる可能性があります。

  • テキストファイルでカウントしたい場合は、@ lanzzの回答のようにロックして、パフォーマンスの大幅な低下に備えてください。これにより、リクエストが効果的にシリアル化されます。
  • ファイルをカウントしたい場合は、SQliteを検討し、管理可能なパフォーマンスヒットに備えてください
  • カウントしたいだけの場合は、パフォーマンスへの影響が非常に小さい実際のDBを検討してください。

編集:実装

テキストファイル、SQLite、MySQLのファイルカウンター用に以下の実装を作成しました

関数ファミリーを使用することで私を怒らせないでくださいmysql_*()-常にコードは生産的ではなく、有益であるように意図されています:周囲の層ではなく、目前の問題に集中するという意味で有益です。

counter-file.php:

<?php

//acquire file handle
$fd=fopen('counter.txt','c+b');
if (!$fd) die("Can't acquire file handle");

//lock the file - we must do this BEFORE reading, as not to read an outdated value
if (!flock($fd,LOCK_EX)) die("Can't lock file");

//read and sanitize the counter value
$counter=fgets($fd,10);
if ($counter===false) die("Can't read file");
if (!is_numeric($counter)) {
    flock($fd,LOCK_UN);
    die("Value in file '$counter' is not numeric");
}

//increase counter and reconvert to string
$counter++;
$counter="$counter";

//Write to file
if (!rewind($fd)) {
    flock($fd,LOCK_UN);
    die("Can't rewind file handle");
}
$num=fwrite($fd,$counter);
if ($num!=strlen($counter)) {
    flock($fd,LOCK_UN);
    die("Error writing file");
}

//Unlock the file and close file handle
flock($fd,LOCK_UN);
fclose($fd);

printf ("Counter is now %05d",$counter);
?>

counter-sqlite.php:

<?php

//counter.sqlite3 was created with 
//CREATE TABLE counter (counter NUMERIC)
//INSERT INTO counter VALUES (0)

//Open database
$dsn='sqlite:'.dirname(__FILE__).'/counter.sqlite3';
$db=new PDO($dsn);
if (!$db) die("Can't open SQlite database via DBO");

//Make exclusive
$sql="BEGIN EXCLUSIVE TRANSACTION";
if ($db->exec($sql)===false) die("Error starting exclusive transaction");

//Update counter
$sql="UPDATE counter SET counter=counter+1";
if (!$db->exec($sql)) die("Error inserting into database");

//Read value
$sql="SELECT counter FROM counter";
$result=$db->query($sql);
if (!$result) die("Error querying database");
foreach ($result as $row) $counter=$row['counter'];

//Commit
$sql="COMMIT TRANSACTION";
if (!$db->exec($sql)) die("Error committing to database");

//Print result
printf("Counter is now %05d",$counter);

?>

カウンター-mysql.php:

<?php

//mysql database was created with 
//CREATE TABLE counter (counter INT NOT NULL)
//INSERT INTO counter VALUES (0)

//Open database connection and select database 
$db=mysql_pconnect('127.0.0.1','redacted','redacted');
if (!$db) die("Can't open database");
if (!mysql_select_db('redacted', $db)) die("Can't select database");

//Update counter
$sql="UPDATE counter SET counter=counter+1";
$qry=mysql_query($sql,$db);
if (!$qry) die("Error updating database");

//Read value
$sql="SELECT counter FROM counter";
$qry=mysql_query($sql,$db);
if (!$qry) die("Error reading from database");
$counter=mysql_fetch_array($qry,MYSQL_ASSOC);
if (!$counter) die("Error reading result");

//Print result
printf("Counter is now %05d",$counter['counter']);

?>

パフォーマンスに関しては:私は修正されたままです。SQLiteの実装は他の2つよりも100倍遅くなります。これは、1000回のクリックをカウントしSTART EXCLUSIVE TRANSACTIONてテストを終了する以外に何も受け入れなければならなかったためです。ab -n 1000 -c 50 http://127.0.0.1/stackoverflow/counter/counter-sqlite.php

OPに対する私の推奨事項は、MySQLバージョンを使用することです。これは高速で、OSのクラッシュに対して確実にカウンターを保存します。ファイルバージョンのパフォーマンス特性はほぼ同じですが、OSのクラッシュによって非常に簡単に破壊される可能性があります。

于 2012-06-16T16:43:21.147 に答える
3

サーバーがそれを単独で処理する方法はありません。自分でファイルロックを実装する必要があります。参照。

于 2012-06-16T16:38:53.707 に答える
2

以下のコードはコードを実装しますが、ファイルロックがあります。ファイルをロックできない場合は、0.5秒待ってから再試行します。

<?php

    $clicks = file_get_contents("clicks.txt");
    $clicks++;

    $fp = fopen("clicks.txt", "w+");

    while ( !flock($fp, LOCK_EX) ) {    
        usleep(500000); // Delay half a second
    }

    fwrite($fp, $clicks);
    fclose($fp);
    flock($fp, LOCK_UN);

    //give the count to the user
    echo "result: $clicks";

?>
于 2012-06-21T14:42:47.980 に答える
2

あなたが求めている振る舞いは、PHP自体ではなく、ファイルシステムがファイルI/Oをどのように処理するかに依存します。コマンドラインスクリプトにも同じ問題があります(それらの多くが同時に実行できる場合)。

コードには2つの競合状態があります。ファイルを読み取るときと書き込むために開くときの間で、ファイルのディスクの内容が変わる可能性があります。また、書き込み用に開くか、実際にディスクに書き込む(フラッシュする)かによって変わる可能性があります。

最初の競合状態は、古い値を書き込むことを意味します。10から11にインクリメントしますが、別のスレッドはすでに10から11にインクリメントしています。クリックを失います。2番目の競合状態はより微妙です。w+モードはファイルを切り捨てるため、直後に別のスレッドが読み取ろうとすると、空のファイルを読み取り、おそらくカウントがゼロであると見なします。最後に、ファイルの内容が破損する可能性があります。次の一連のイベントについて考えてみます。

  1. スレッド1とスレッド2はどちらも、ファイルから「8」を読み取り、それを開いて書き込み(切り捨て)します。
  2. スレッド1は「9」を書き込みますが、スレッド2は数ミリ秒遅延します。
  3. その間に、スレッド3はファイルを開き、「9」を読み取り、「10」を書き込みます。
  4. 最後に、スレッド2がウェイクアップし、ファイルの先頭に「9」を書き込んで終了します。

結果:ファイルには「90」と表示されます。これは、異なる長さのデータが同時に書き込まれる場合は常に実際の問題です。

結論:同時アクセスがないと合理的に予想できる場合を除いて、ディスクファイルの状態を維持しないでください。訪問者が少なく、その間の距離が遠い場合は、問題ありません。ただし、猫の動画が口コミで広まると、カウンターがスクランブルされ、保護者にサーバーログを確認して、ページへのヒット数をカウントするように依頼する必要があります。

代わりにデータベースを使用してください。データベースは同時実行性を処理するように設計されています。MySQLデータベースへのアクセスを手配します(親がサーバー上で重要な権限を与える必要はありません)。それをしなくても、コンピューターに私用のデータベースをインストールすることで、データベースの使い方を学ぶことができます。Windowsコンピュータをお持ちの場合、easyPHPは、1回のクイックダウンロードでプライベートWebサーバー、php、およびMySQLを利用するための簡単な方法です。

于 2012-06-22T12:35:20.780 に答える
1

あなたの例では、2つのロックの問題が発生する可能性があります。

  • クリック数は0にリセットできます
  • クリックは別のクリックで消去できます

flockメソッド(http://php.net/manual/en/function.flock.php)を使用してファイルをロックできます。ロックが失敗した場合は、再試行する必要があります。

于 2012-06-16T16:42:56.753 に答える
0

衝突を回避する最善の方法は、ログシステムを使用することだと思います。このログライブラリを試すことができます:http://pear.php.net/package/Log/docs/latest/Log/Log.html

于 2012-06-23T10:29:37.083 に答える
0

スクリプトが同時に実行される場合は、2回クリックするのではなく、1回クリックするだけです。この操作にはデータベースを使用することをお勧めします。

実際には、Webサイトのトラフィックが少ないと予想される場合は、このスクリプトが適切に機能します。ただし、負荷が高くなると失敗します。

于 2012-06-16T16:44:11.040 に答える
0

ほとんどの人はそのような方法に同意しませんが、同時リクエストには問題があります。解決策として、排他的なファイルロックを利用できます。

例を確認してください:http ://www.php.net/manual/en/function.flock.php

于 2012-06-20T19:43:22.663 に答える