使用PHP访问没有数据库的计数器


Visits counter without database with PHP

我有一个网页,我想在不使用数据库的情况下跟踪它的访问次数。

我想到了XML,每次用户访问页面时都会更新一个文件:

<?xml version='1.0' encoding='utf-8'?>
<counter>8</counter>

然后我想,最好在一个单独的文件中声明一个PHP计数器,然后在每次用户访问页面时更新它。

计数器.php

<?php
    $counter = 0;
?>

update_counter.php:

<?php
    include "counter.php";
    $counter += 1;
    $var = "<?php'n't'$counter = $counter;'n?>";
    file_put_contents('counter.php', $var);
?>

这样,每次访问update_counter.php时,counter.php文件中的变量都会递增。

无论如何,我注意到,如果counter.php文件具有$counter = 5,并且update_counter.php文件被1000个用户同时访问,则该文件在同一时间被读取1000次(因此在所有请求中都读取值5),则counter.php文件将被更新为值5+1 (=6)而不是1005

有没有一种方法可以让它在不使用数据库的情况下工作?

您可以使用flock()来锁定文件,这样其他进程就不会写入该文件。

编辑:更新为使用fread()而不是include()

$fp = fopen("counter.txt", "r+");
while(!flock($fp, LOCK_EX)) {  // acquire an exclusive lock
    // waiting to lock the file
}
$counter = intval(fread($fp, filesize("counter.txt")));
$counter++;
ftruncate($fp, 0);      // truncate file
fwrite($fp, $counter);  // set your data
fflush($fp);            // flush output before releasing the lock
flock($fp, LOCK_UN);    // release the lock
fclose($fp);
<?php 
   /** 
   * Create an empty text file called counterlog.txt and  
   * upload to the same directory as the page you want to  
   * count hits for. 
   *  
   * Add this line of code on your page: 
   * <?php include "text_file_hit_counter.php"; ?> 
   */ 
  // Open the file for reading 
  $fp = fopen("counterlog.txt", "r"); 
  // Get the existing count 
  $count = fread($fp, 1024); 
  // Close the file 
  fclose($fp); 
  // Add 1 to the existing count 
  $count = $count + 1; 
  // Display the number of hits 
  // If you don't want to display it, comment out this line    
  echo "<p>Page views:" . $count . "</p>"; 
  // Reopen the file and erase the contents 
  $fp = fopen("counterlog.txt", "w"); 
  fwrite($fp, $count); 
  // Close the file 
  fclose($fp); 
 ?> 

这听起来很容易,但很难解决。原因是比赛条件。

比赛条件是什么
如果你打开一个计数器文件,读取内容,增加点击量,并将点击量写入文件内容,那么通过其他访问者同时在你的网站上打开相同的脚本,在所有这些步骤之间可能会发生很多事情。想想第一个访问者请求(线程)逐个字符地向计数器文件写入"484049"命中率时的情况,在写入"484"的毫秒内,第二个线程读取该值并将其递增到"485",从而失去了大部分不错的命中率。

不要使用全局锁
也许您可以考虑使用LOCK_EX来解决这个问题。到那时,第二个线程需要等待,直到第一个线程完成对文件的写入。但"等待"并不是你真正想要的。这意味着每个线程,我的意思是每个线程都需要等待其他线程。你只需要在你的网站上有一些疯狂的机器人,很多访问者,或者你的驱动器上有一个临时的i/o问题,在所有写入完成之前,没有人能够加载你的网站。。。如果访问者无法打开你的网站会发生什么。。。他将刷新它,导致新的等待/锁定线程。。。瓶颈

使用基于线程的锁
唯一安全的解决方案是立即为同时运行的线程创建一个新的计数器文件:

<?php
// settings
$count_path = 'count/';
$count_file = $count_path . 'count';
$count_lock = $count_path . 'count_lock';
// aquire non-blocking exlusive lock for this thread
// thread 1 creates count/count_lock0/
// thread 2 creates count/count_lock1/
$i = 0;
while (file_exists($count_lock . $i) || !@mkdir($count_lock . $i)) {
    $i++;
    if ($i > 100) {
        exit($count_lock . $i . ' writable?');
    }
}
// set count per thread
// thread 1 updates count/count.0
// thread 2 updates count/count.1
$count = intval(@file_get_contents($count_file . $i));
$count++;
//sleep(3);
file_put_contents($count_file . $i, $count);
// remove lock
rmdir($count_lock . $i);
?>

现在,您的计数器文件夹中有count/count.1count/count.2等,而count.1将获得大部分命中率。原因是比赛条件并非总是发生。只有当两个线程同时存在时,它们才会发生。

注意:如果你看到(远远)超过2个文件,这意味着与你的访客数量相比,你的服务器真的很慢。

如果你现在想要总点击量,你需要整理它们(在这个例子中是随机的):

<?php
// tidy up all counts (only one thread is able to do that)
if (mt_rand(0, 100) == 0) {
    if (!file_exists($count_lock) && @mkdir($count_lock)) {
        $count = intval(@file_get_contents($count_file . 'txt'));
        $count_files = glob($count_path . '*.*');
        foreach ($count_files as $file) {
            $i = pathinfo($file, PATHINFO_EXTENSION);
            if ($i == 'txt') {
                continue;
            }
            // do not read thread counts as long they are locked
            if (!file_exists($count_lock . $i) && @mkdir($count_lock . $i)) {
                $count += intval(@file_get_contents($count_file . $i));
                file_put_contents($count_file . $i, 0);
                rmdir($count_lock . $i);
            }
        }
        file_put_contents($count_file . 'txt', $count);
        rmdir($count_lock);
    }
}
// print counter
echo intval(@file_get_contents($count_file . 'txt'));
?>

p.S.启用sleep(3)并查看计数器文件夹以模拟慢速服务器,您可以看到多个计数文件的增长速度有多快。

<?php 
 $File = "counter.txt"; 
 //This is the text file we keep our count in, that we just made
 $handle = fopen($File, 'r+') ; 
 //Here we set the file, and the permissions to read plus write
 $data = fread($handle, 512) ; 
 //Actully get the count from the file
 $count = $data + 1;
 //Add the new visitor to the count
 print "You are visitor number ".$count; 
 //Prints the count on the page
?>

除了文件大小太大,访问次数太多之外,以下操作效果很好。

file_put_contents('counter.txt', '1', FILE_APPEND);
echo '<h1>Hi, Page served ' . filesize('counter.txt') . ' times!</h1>';

但是,在文件达到1000或1000000之后,只需创建另一个同样计算该单位的文件。大尺寸的不确定性与不需要锁定的性能相匹配。