今天我只是想知道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();
答案 0 :(得分:1)
您考虑的事项和代码示例不是线程安全的。这不是PHP问题,而是并发一般。
解决方案是:
对于样本1和2等文件操作,请使用文件锁。
用于您的资金交易等操作,使用数据库交易或最终使用表锁。
据我所知,PHP不提供信号量机制。 Remebmer,服务器或配置(如apache prefork / worker)的内部实现甚至可以在另一个进程中生成每个请求 - 因此您不必担心共享内存。担心资源 - 文件,数据库等。
你提到的这种信号量不是一个好的解决方案。例如,在数据库级别,db引擎可以锁定/解锁单个表甚至行,与“在该段代码上锁定整个服务器”相比,这是非常有效的。
答案 1 :(得分:0)
+1 to killer_PL以及他的回答:
Memcache cas()
或add()
函数非常便于文件锁定实现。
Add()
才会使用某个密钥存储变量。 Cas()
还执行“检查和设置”操作。
基于这些操作之一设计信号量非常容易。
答案 2 :(得分:0)
抱歉我的英文。
首先,我要描述一些一般特征:
这是真的=)
$semaphore->lock();
// Do something dangerous
$semaphore->unlock();
我试图描述基本概念。代码不适合发布
首先让我们实现文件。我们将使用嵌入式函数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