У меня есть одна веб-страница, и я хотел бы отслеживать, сколько раз она была посещена без использования базы данных.
Я думал о 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
а файл 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», обращается к символу-счетчику с помощью 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.1
, count/count.2
и т. Д. В вашей папке счетчика, в то время как count.1
будет захватывать большинство хитов. Причиной этого является то, что условия гонки не происходят все время. Они происходят только в том случае, если два потока одновременно.
Примечание. Если вы видите (много) более двух файлов, это означает, что ваш сервер работает очень медленно по сравнению с количеством посетителей, которое у вас есть.
Если вам сейчас нужны полные хиты, вам нужно их убрать (в этом примере случайным образом):
<?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')); ?>
PS включить 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 ?>