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

时间:2013-08-14 15:56:32

标签: php counter

我有一个网页,我想跟踪没有使用数据库的次数。

我考虑过XML,每次用户访问页面时都会更新文件:

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

然后我认为在一个单独的文件中声明一个PHP计数器然后每次用户访问该页面时更新它都是一个更好的主意。

counter.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并且{1000}用户在同一时间访问update_counter.php文件,则该文件将被读取1000次同一时间(因此在所有请求中读取值5counter.php文件将使用值5+1 (=6)而不是1005进行更新。

有没有办法让它在不使用数据库的情况下工作?

5 个答案:

答案 0 :(得分:6)

您可以使用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);

答案 1 :(得分:6)

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

 ?> 

答案 2 :(得分:2)

听起来很简单,但真的难以解决。原因是race-conditions

什么是竞争条件?
如果您打开计数器文件,阅读内容,增加命中并将命中数写入文件内容,所有这些步骤之间可能会发生很多事情,通过其他访问者同时在您的网站上打开相同的脚本。考虑当第一个访问者请求(线程)将“484049”命中写入char计数器文件时的情况,并且在毫秒内写入“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)并查看计数器文件夹以模拟速度较慢的服务器,您可以看到多个计数文件的增长速度。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

以下内容的工作原理很不错,除了文件太多导致访问过多。

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

但是,文件达到1000或1000000后,只需创建另一个也计算该单位的文件即可。大尺寸的优雅与不需要锁定的性能相匹配。