我写了这个计数器,但是在每小时大约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”不会在网站上的任何其他位置使用。我怎样才能改进这门课程?
答案 0 :(得分:1)
我不知道~120500号码的来源。我要在这里解决竞争状况。
有几点需要注意:
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);
}