PHP如何同时管理请求

时间:2013-03-28 11:29:36

标签: php multithreading security

今天我只是想知道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 个答案:

答案 0 :(得分:1)

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

解决方案是:

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

  • 用于您的资金交易等操作,使用数据库交易或最终使用表锁。

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

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

答案 1 :(得分:0)

+1 to killer_PL以及他的回答:

Memcache cas()add()函数非常便于文件锁定实现。

仅当服务器上不存在此类密钥时,

Add()才会使用某个密钥存储变量。 Cas()还执行“检查和设置”操作。  基于这些操作之一设计信号量非常容易。

答案 2 :(得分:0)

抱歉我的英文。

首先,我要描述一些一般特征:

  1. 这是真的=)

    $semaphore->lock();

    // Do something dangerous

    $semaphore->unlock();

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

  3. 信号量应该有一些类型:文件数据库和 其他必要的。
  4. 每种类型的实现都会有所不同。
  5. 首先让我们实现文件。我们将使用嵌入式函数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();
    

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

答案 3 :(得分: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