服务器是否会同时处理两个或更多?

时间:2012-06-16 16:35:23

标签: php

我在PHP中使用此代码将视图写入文本文件并仅增加数量。我的问题是:如果两个或更多人使用我的网站并运行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";

    ?>

8 个答案:

答案 0 :(得分:6)

首先:这不是我写点击计数器的方式。

也就是说,100个用户同时点击您的服务器(初始点击次数为0)可能会导致记录的数字为1..100,其中低(=错误)值显着。

  • 如果您想要计入一个文本文件,请锁定@ lanzz的答案,并为主要的性能提升做好准备 - 您有效地序列化了这些请求。
  • 如果您想要计入任何文件,请考虑使用SQlite并为可管理的性能提升做好准备
  • 如果您只想计算,请考虑一个效果非常小的真正数据库

编辑:实施

我在文本文件SQLite和MySQL

中为文件计数器创建了以下实现

请不要因为使用mysql_*()函数系列而激怒我 - 因为总的来说,代码是有指导意义的,而不是高效的:在专注于手头的问题而非周围层的意义上具有指导意义。< / p>

<强>反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);
?>

<强>反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实施速度比其他两个慢100倍 - 这是因为我必须接受,除了START EXCLUSIVE TRANSACTION之外别无其他ab -n 1000 -c 50 http://127.0.0.1/stackoverflow/counter/counter-sqlite.php的测试,点击了1000次点击。

我对OP的建议是使用MySQL版本 - 它很快并且可以在操作系统崩溃时可靠地保存计数器。文件版本具有几乎相同的性能特征,但它很容易被操作系统崩溃破坏。

答案 1 :(得分:3)

服务器无法自行处理它。您需要自己实现文件锁定。 Reference.

答案 2 :(得分:2)

下面的代码将实现您的代码,但文件锁定。 当文件无法锁定时,它将等待半秒钟再试一次。

<?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";

?>

答案 3 :(得分:2)

您所询问的行为取决于文件系统如何处理文件i / o,而不是PHP本身。命令行脚本会遇到同样的问题(如果它们中的许多可以同时运行)。

您的代码受两种竞争条件的限制:文件的磁盘内容可能会在您读取文件的时间和打开文件的时间之间发生变化。它可能会在开放写入和实际写入(刷新)到磁盘之间发生变化。

第一个竞争条件意味着你将写一个陈旧的值:你从10增加到11,但另一个线程已经从10增加到11.你失去了一个点击。第二种竞争条件更加微妙:因为w+模式截断了文件,如果另一个线程稍后尝试读取它将读取一个空文件,并且可能认为计数为零。最后,您的文件内容可能会被破坏。考虑以下事件序列:

  1. 线程1和线程2都从文件中读取“8”,然后将其打开以进行写入(截断)。
  2. 线程1写入“9”,但线程2延迟了几毫秒。
  3. 同时,线程3打开文件,读取“9”,并写入“10”。
  4. 最后,线程2醒来,将“9”写入文件的开头并退出。
  5. 结果:文件现在显示“90”。每当同时写入不同长度的数据时,这是一个真正的问题。

    结论:除非您可以合理地预期不会有并发访问,否则不要在磁盘文件中维护状态。如果游客很少而且很远,没问题。但是如果你的猫视频病毒式传播,你的计数器就会被打乱,你必须要求你的父母检查他们的服务器日志并计算你网页的点击数。

    使用数据库:数据库旨在处理并发。安排访问MySQL数据库(要求您的父母在服务器上授予您重要权利)。如果没有这样做,您可以通过在计算机上安装专用数据库来了解数据库。如果您有一台Windows计算机,easyPHP可以快速下载,帮助您自己访问私人网络服务器,PHP和MySQL。

答案 4 :(得分:1)

在您的示例中,您可能遇到两个锁定问题:

  • 点击次数可以重置为0
  • 点击次数可以通过点击次数删除

您可以使用flock方法锁定文件(http://php.net/manual/en/function.flock.php)。 如果锁定失败,则需要重试。

答案 5 :(得分:0)

AFAIK如果脚本同时执行,那么它不会给你两次点击,只有一次。 您最好使用数据库进行此操作。

实际上,如果您希望自己的网站流量较低,那么这个脚本会很好用。但它会在更高的负载下失败。

答案 6 :(得分:0)

大多数人都不同意这种方法,但是你会遇到并发请求的问题。作为解决方案,您可以使用独占文件锁定。

查看示例:http://www.php.net/manual/en/function.flock.php

答案 7 :(得分:0)

我认为避免碰撞的最佳方法是使用日志系统。您可以尝试此日志库:http://pear.php.net/package/Log/docs/latest/Log/Log.html