PHP 多个客户端访问同一个文件

PHP multiple clients accessing same file

我开始玩 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。

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

考虑到您的流量,我认为您的要求应该得到实施。

如果您的流量较低,实施基于锁的计数器不是问题。因为并发访问同一个文件的概率很低,打开、写入和关闭文件只需要几毫秒。

另一种解决方案可能是使用 memcached、redis 或 APC 缓存机制,并在密钥库中保留单个计数器。

如果您考虑每秒几百万次点击,则无法将其托管在单个服务器中。它很可能是通过负载均衡器进行扩展并托管在不同的 regions/servers 中。然后应该像消息队列一样实现非阻塞服务的命中计数器。如果您对排队计数器感兴趣,可以在 rabbitmq, or 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

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