4

今天我只是想知道 PHP 如何同时处理请求。由于 PHP 可以同时处理多个请求,我想到了 PHP 脚本中可能存在的安全漏洞或错误,我只是想知道我是否有点太害怕了。

因此,如果同时有 100 个请求,并且 apache 配置为将它们转发到 PHP。PHP 将如何处理以下示例(我已经在某些现实世界的应用程序中以某种方式看到的所有示例)

所有示例彼此相似。(我要求更好的方法来解决这些示例案例)


示例 1:创建缓存

<?php
if (!file_exists('my_cache.txt')) {
    // do something slow (taking a second or so)
    file_put_contents('my_cache.txt', $cache);
}

假设我们有大约 100 个请求。缓存是不是生成了100次,在缓存文件中存储了100次?


示例 2:将条目写入缓存

<?php
writeItemToDatabase($myItem);

if (countAllItemsInDatabase() > 100) {
    $items = readAllItemsFromDatabase();
    deleteAllItemsFromDatabase();
    // Process items
}

由于“deleteAllItemsFromDatabase”函数,这个例子有点愚蠢。如果此脚本将并行执行,则可能会发生以下情况:

  • 两个用户同时处理所有项目
  • 有些项目永远不会被处理,因为它们在任何时候被处理之前就被删除了。

示例 3:虚拟货币

<?php
if ($user->getMoney() > 100) {
    $user->decreaseMoney(100);
    $user->addItem($itemToBuy);
}

如果脚本可能同时运行,则此示例存在很大的安全问题。如果我快速点击此应用程序的“购买”按钮,即使我的用户帐户上没有钱,我也可以购买物品。


问题

我想知道我是否只是有点偏执于编写脚本以防止此类问题,或者这些示例是真正的问题吗?

并且 - 对于极少数情况 - 如果我需要编写一些动作处理序列(如在那些示例中),是否有 PHP 函数/扩展来确保一次只处理一次脚本部分,例如:

<?php
$semaphore->lock();
// Do something dangerous
$semaphore->unlock();
4

4 回答 4

1

您考虑的事情和代码示例不是线程安全的。这不是 PHP 问题,而是一般并发问题。

解决方案是:

  • 对于示例 1 和 2 等文件操作,请使用文件锁。

  • 对于您的货币交易等操作,请使用数据库交易或最终的表锁。

据我所知,PHP 不提供信号量机制。请记住,服务器的内部实现或配置(如 apache prefork/worker)甚至可以在另一个进程中生成每个请求 - 因此您不必担心共享内存。担心资源——文件、数据库等。

您提到的此类信号量不是一个好的解决方案。例如在数据库级别,数据库引擎可以锁定/解锁单个表甚至行,这与“将整个服务器锁定在那段代码”相比非常有效。

于 2013-03-28T13:01:11.097 回答
0

+1 给killer_PL,除了他的回答:

Memcachecas()add()函数对于文件锁定的实现非常方便。

Add()仅当服务器上尚不存在此类密钥时,才使用特定密钥存储变量。Cas()还执行“检查和设置”操作。基于这些操作之一设计信号量是非常容易的。

于 2013-03-28T13:13:05.030 回答
0

对不起我的英语不好。

首先,我将描述一些一般特性:

  1. 这是真的=)

    $semaphore->lock();

    // Do something dangerous

    $semaphore->unlock();

  2. 我试图描述基本概念。代码不适合发布

  3. 信号量应该有一些类型:文件数据库和其他必要的。
  4. 每种类型的实现都会有所不同。

首先让我们进行文件实现。我们将使用嵌入式函数flock(感谢 Salman A)。

<?php
$fname = 'test.txt';

$file = fopen($fname, 'a+');
sleep(5); // long operation
if(flock($file,LOCK_EX|LOCK_NB )){// we get file lock - $semaphore->lock();

  sleep(5); // long operation
  fputs($file, "\n".date('d-m-Y H:i:s')); //something dangerous
  echo 'writed';
  flock($file,LOCK_UN ); // release lock - $semaphore->unlock();
}else{
  // file already locked
  echo 'LOCKED';
}
fclose($file);

其次,让我们进行数据库锁定。一般来说,一些数据库可能有锁定单个表记录的机制,在这种情况下你应该使用这种机制。但其他数据库不支持该功能,例如 MySql。对于这种情况,让我们做一些魔术:)

例如我们有简单的表

 CREATE TABLE `threading` (
        `id` INT(10) NOT NULL AUTO_INCREMENT,
        `val` INT(10) NOT NULL,
        PRIMARY KEY (`id`)
    )COLLATE='utf8_general_ci'
    ENGINE=InnoDB

让我们添加列,它将模拟“锁定”记录:

ALTER TABLE `threading`
    ADD COLUMN `_lock` BIT NOT NULL AFTER `val`;

现在我们可以将 _lock fild 设置为 1 来锁定记录!

重要提示:您必须通过这样的单个查询来锁定:update threading set _lock = 1 where id = 1 AND _lock <> 1 ;.
注意:AND _lock <> 1防止记录在已经被锁定的情况下被锁定,这样可以通过rows_affected机制来解析记录是否被锁定。

<?php

// connect
mysql_connect('localhost','root','root');
mysql_selectdb('testing');

// get info
$res = mysql_query('select * from threading where id = 1;');
$row = mysql_fetch_assoc($res);

print_r($row); // debug

if($row['val']>=70){
  sleep(5); // emulate long-long operation =)

  // try to lock
  mysql_query('update threading set _lock = 1 where id = 1 AND _lock <> 1 ;'); // _lock <> 1 - very IMPORTANT!
  sleep(5); // emulate long-long operation =)
  $affected_rows = mysql_affected_rows();
  if($affected_rows!=1){  
    // lock failed -  locked by another instance
    echo '<br> LOCKED!';
  }else{
    // lock succeed
    mysql_query('update threading set val = val-70 where id = 1;');//something dangerous

    mysql_query('update threading set _lock = 0 where id = 1;'); // UNLOCK!
  }

}

// view result
$res = mysql_query('select * from threading where id = 1;');
$row2 = mysql_fetch_assoc($res);

echo '<br>';
print_r($row2);

// disconnect
mysql_close();

因此,测试非常简单——同时在不同的浏览器中运行文件。对于另一种类型的信号量,您应该使用其他逻辑和功能

于 2013-03-28T13:51:24.083 回答
0

https://github.com/mpapec/simple-cache/blob/master/example3.php

require "SafeCache.class.php";


// get non blocking exclusive lock
$safe = new SafeCache("exclusive_lock_id");

if ( $safe->getExclusive() ) {
  print "we have exclusive lock now<br>";

  // ...

  print "releasing the lock<br>";
  $safe->doneExclusive();
}

此外,请查看其他安全缓存生成示例。 https://github.com/mpapec/simple-cache/blob/master/example1.php

于 2013-03-29T07:27:18.560 回答