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」関数のため、この例は少しばかげています。このスクリプトを並行して実行すると、次のことが起こります。

  • 2 人のユーザーがすべてのアイテムを同時に処理する
  • 一部のアイテムは処理される前に削除されるため、処理されません。

例 3: 仮想通貨

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

スクリプトが同時に実行される可能性がある場合、この例には大きなセキュリティ上の問題があります。このアプリケーションの「購入」ボタンをすばやく押すと、ユーザー アカウントにお金が残っていなくてもアイテムを購入できる可能性があります。


質問

このような問題を防ぐためにスクリプトを書くことに少し偏執的であるか、それともこれらの例は実際の問題なのでしょうか?

そして、まれなケースですが、(これらの例のように)アクション処理されたシリアルを記述する必要がある場合、次のように、スクリプト部分を一度に1回だけ処理することを保証するPHP関数/拡張機能はありますか?

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

4 に答える 4

1

考慮事項とコード サンプルはスレッド セーフではありません。これは PHP の問題ではなく、一般的な同時実行の問題です。

解決策は次のとおりです。

  • サンプル 1 および 2 のようなファイル操作には、ファイル ロックを使用します。

  • お金の取引などの操作では、データベーストランザクションを使用するか、最終的にはテーブル ロックを使用します。

私が知っているように、PHP はセマフォ機構を提供していません。サーバーの内部実装、または構成 (apache prefork/worker など) は、別のプロセスですべての要求を生成することもできるため、共有メモリについて心配する必要はありません。リソースに関する心配 - ファイル、データベースなど

あなたが言及したそのようなセマフォは、良い解決策ではありません。たとえば、データベース レベルでは、db エンジンは個々のテーブルまたは行をロック/ロック解除できます。これは、「そのコードでサーバー全体をロックする」よりも非常に効率的です。

于 2013-03-28T13:01:11.097 に答える
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 フィールドを 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

killer_PLに+1し、彼の答えに加えて:

Memcachecas()またはadd()関数は、ファイル ロックの実装に非常に便利です。

Add()そのようなキーがまだサーバーに存在しない場合にのみ、特定のキーで変数を保存します。Cas()また、「チェック アンド セット」操作も実行します。これらの操作の 1 つに基づいてセマフォを設計するのは非常に簡単です。

于 2013-03-28T13:13:05.030 に答える
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 に答える