计数器重置为相同的非零值?

时间:2013-10-18 06:46:59

标签: php file-io counter

我写了这个计数器,但是在每小时大约100-200个独特点击的负载下有一个奇怪的问题。计数重置为大约“120500”的奇怪值,并从那里继续计数,直到稍后重置为相同的值。现在,我会理解零值,但120500来自哪里?这是完整的计数器代码:

<?php

    class Counter {
        public $currentCount;
        private $countFile;

        public function __construct($file) {
            if(!file_exists($file)) {
                $fp = fopen($file, 'w');
                fwrite($fp, '1');
                fclose($fp);
            }
            $this->countFile = $file;
            $this->currentCount = file_get_contents($this->countFile);
        }

        public function incrementPerSession() {
            if(isset($_SESSION['visitWritten'])) {
                echo $this->currentCount;
            } else {
                $count = $this->currentCount + 1;
                $this->writeNewCount($count);
                echo $count;
            }
        }

        private function writeNewCount($count) {
            $delay = rand(10000, 80000);
            $fp = fopen($this->countFile, 'w');
            if(flock($fp, LOCK_EX)) {   // PHP locks are not reliable.
                usleep($delay);         // usleep() works as a workaround and prevents resets to zero
                fwrite($fp, $count);
                flock($fp, LOCK_UN);
                $_SESSION['visitWritten'] = true;
            } else {
                echo 'Counter: Could not write to file';
            }
            fclose($fp);
        }
    }
?>

这种情况偶尔发生,我认为这与同时写入有关。会话变量“visitWritten”不会在网站上的任何其他位置使用。我怎样才能改进这门课程?

1 个答案:

答案 0 :(得分:1)

我不知道~120500号码的来源。我要在这里解决竞争状况。

有几点需要注意:

  • linux中的文件锁定是 advisory * - 为了相处,一切都要好好玩,玩得好是选择加入而不是选择退出
  • file_get_contents()并不好玩。如果文件已被锁定,那么哦 - 它无论如何都会被读取。该功能甚至不知道锁存在哪里。
  • fopen($file, w)会在您锁定文件之前立即截断该文件。

* 可以使用文件系统挂载选项强制使用它,但我从未见过使用它。

那该怎么办。让我们从写作开始吧。我们的想法是延迟截断文件,直到我们将文件锁定为止。

写:

$fh = fopen($file, a+); // Open the file in append-or-create mode.
                        // You may as well use the same function for
                        // creating the file as you do to update it.

if (flock($fh, LOCK_EX)) {
    rewind($fh);        // Set the file pointer to the start of the file.

    ftruncate($fh, 0);  // Truncate the file. This is safe since we've got a lock.
                        // Or will be safe, once we make everything respect locks.

    fwrite($fh, $string);
    flock($fh, LOCK_UN);
}
fclose($fh);

现在,即使在我们打开文件和锁定文件之间读取文件,他们也会获取数据,因为我们还没有截断它。我们所要做的就是获取文件读取以尊重锁定,所有这些都应该是安全的。

读取:

$fh = fopen($file, 'r');
if (flock($fh, LOCK_SH)) { // Play nice. Get a shared lock. This will block
                           // if another process has an exclusive lock.
    $string = fread($fh, filesize($file));
    flock($fh, LOCK_UN);
}
fclose($fh);

除了应该做的一些错误处理。

除了仍然存在竞争条件。

如果两个进程正在执行&#34; read - &gt;增量 - &gt;写&#34;同时循环它们有可能同时读取相同的数据,然后写入相同的数据。它没有计数器重置那么糟糕,但它是一个错过的命中。为了解决这个问题,您希望将整个周期包装在一个独占锁中。因此,基本上你最终会使用以下内容来使用它:

if(isset($_SESSION['visitWritten'])) {
    echo read($this->countFile);
} else {
    echo readAndIncrementAndUpdate($this->countFile);
}