Несколько клиентов PHP, обращающихся к одному и тому же файлу

Я начал играть с PHP и увидел этот фрагмент кода, который должен действовать как счетчик посещений для веб-страницы:

/* counter */

//opens countlog.txt to read the number of hits
$datei = fopen("/countlog.txt","r");
$count = fgets($datei,1000);
fclose($datei);
$count=$count + 1 ;
echo "$count" ;
echo " hits" ;
echo "\n" ;

// opens countlog.txt to change new hit number
$datei = fopen("/countlog.txt","w");
fwrite($datei, $count);
fclose($datei);

Основываясь на том, что я прочитал, несколько запросов могут выполняться одновременно на сервере. Так что есть шанс, что они получат доступ к этому файлу countlog.txt одновременно (правильно?). Если это так, этот код не работает для загруженного веб-сайта (правильно?). Как можно изменить этот код, чтобы он работал на загруженном веб-сайте? Можете ли вы использовать блокировки в PHP, которые используются несколькими запросами?

PS: Вопрос НЕ о счетчиках. Пожалуйста, избегайте использования SQL в ответе, если это возможно.


person Kasra    schedule 14.12.2015    source источник


Ответы (2)


Вы можете использовать flock() для получения эксклюзивной блокировки файла, см. http://php.net/manual/en/function.flock.php

$fp = fopen("/tmp/lock.txt", "r+");

if (flock($fp, LOCK_EX)) {
    $datei = fopen("/countlog.txt","r");
    $count = fgets($datei,1000);

    $count=$count + 1 ;
    echo "$count" ;
    echo " hits" ;
    echo "\n" ;

    ftruncate($fp, 0);
    fwrite($fp, $count);
    fflush($fp);
    flock($fp, LOCK_UN);
} else {
    echo "Could not lock file!";
}

fclose($fp);

Возможно, вам следует создать цикл, ожидающий успешной блокировки с помощью usleep(), чтобы избежать активного ожидания: http://php.net/manual/en/function.usleep.php

person maxhb    schedule 14.12.2015
comment
Согласно документам, flock будет блокироваться до тех пор, пока не будет получена блокировка. Почему вы предлагаете использовать цикл (предполагая цикл while)? - person Kasra; 14.12.2015
comment
Извините, не упомянул об этом: я обычно использую flock() с опцией LOCK_NB. Это дает мне возможность избежать взаимоблокировок. Например. можно было только попытаться получить замок 100 раз, а затем сдаться. - person maxhb; 14.12.2015

Я думаю, что ваше требование должно быть реализовано с учетом вашего трафика.

Если у вас низкий трафик, внедрение счетчика на основе блокировки не может быть проблемой. Потому что вероятность одновременного доступа к одному и тому же файлу очень мала, а открытие, запись и закрытие файла занимает несколько миллисекунд.

Другим решением может быть использование механизма кэширования memcached, redis или APC и сохранение одного счетчика в хранилище ключей.

Если вы рассматриваете несколько миллионов обращений в секунду, это не может быть размещено на одном сервере. Скорее всего, он масштабируется с помощью балансировщика нагрузки и размещается в разных регионах/серверах. Затем счетчик посещений должен быть реализован неблокируемым сервисом, таким как очередь сообщений. Если вас интересует постановка счетчика посещений в очередь, вы можете узнать больше на rabbitmq, или activemq

RabbitMQ и ActiveMQ поддерживают следующие протоколы и многие другие протоколы, и вы можете найти множество клиентских библиотек php для подключения по этим протоколам.

Несколько примеров кода

Использование APC в качестве счетчика

<?php
apc_add('counter', 0);
echo apc_inc('counter')
?>

Использование Memcached

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);

$m->add('counter', 0);
$m->increment('counter');
?>

RabbitMQ и php-amqplib

композитор.json

{
    "require": {
        "videlalvaro/php-amqplib": "2.5.*"
    }
}

$ composer.phar install

<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();

$channel->queue_declare('counter', false, false, false, false);

$callback = function($msg) {
  // $msg->body has the content of the message
  // counter update implementation goes here
};

$channel->basic_consume('counter', '', false, true, false, false, $callback);

while(count($channel->callbacks)) {
    $channel->wait();
}
?>
person Pradeep Sanjaya    schedule 14.12.2015