Kasra
Kasra

Reputation: 3145

PHP multiple clients accessing same file

I started playing with PHP, and saw this code snippet that is supposed to act a hit counter for a webpage:

/* 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);

Based on what I read, multiple requests can run simultaneously on the server. So there is chance that they access this file countlog.txt at the same time (correct?). If so, this code doesn't work for a busy website (correct?). How would one change this code to make it work for a busy website? Can you use locks in PHP that is shared between multiple request?

PS: The question is NOT about counters. Please avoid using SQL in the answer if possible.

Upvotes: 2

Views: 1842

Answers (2)

Pradeep Sanjaya
Pradeep Sanjaya

Reputation: 1846

I think your requirement should be implemented considering your traffic.

If your traffic is low, implementing lock based counter couldn’t be a issue. Because probability of concurrent access to a same file is very low and it takes few milliseconds to open, write and close the file.

Another solution could be using a memcached, redis or APC caching mechanism and keep a single counter in a key store.

If you are considering few million hits per second, it couldn't be hosted in a single server. Most probably it is scaled with a load balancer and hosted in different regions/servers. Then a hit counter should be implemented non blocked service like a messaging queue. If you interested about queuing your hit counter you can read more on rabbitmq, or activemq

RabbitMQ and ActiveMQ supports following protocols and many other protocols and you can find many php client libraries to connect over those protocols.

Few code samples

Using APC as counter

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

Using Memcached

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

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

RabbitMQ and php-amqplib

composer.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();
}
?>

Upvotes: 3

maxhb
maxhb

Reputation: 8865

You could use flock() to get an exclusive lock on a file, see 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);

Maybe you should build a loop that waits for a successfull lock using usleep() to avoid active waiting: http://php.net/manual/en/function.usleep.php

Upvotes: 0

Related Questions