web-dev-qa-db-ja.com

PHP file_put_contentsファイルのロック

セナリオ:

あなたは各行に文字列(平均文の価値)を持つファイルがあります。議論のために、このファイルのサイズが1Mb(数千行)であるとしましょう。

ファイルを読み取り、ドキュメント内の一部の文字列を変更し(追加するだけでなく、一部の行を削除および変更する)、すべてのデータを新しいデータで上書きするスクリプトがあります。

質問:

  1. 「サーバー」のPHP、OS、またはhttpdなどには、このような問題(書き込みの途中で読み取り/書き込み)を停止するためのシステムがすでに整っていますか?

  2. 機能する場合は、その仕組みを説明し、関連するドキュメントへの例やリンクを提供してください。

  3. そうでない場合、書き込みが完了するまでファイルをロックし、前のスクリプトが書き込みを完了するまで他のすべての読み取りや書き込みを失敗させるなど、有効化または設定できるものはありますか?

私の仮定およびその他の情報:

  1. 問題のサーバーは、PHPおよびApacheまたはLighttpdを実行しています。

  2. スクリプトが1人のユーザーによって呼び出され、ファイルへの書き込みの途中であり、別のユーザーがその瞬間にファイルを読み取る場合。まだ書かれていないため、それを読んだユーザーは完全なドキュメントを取得できません。 (この仮定が間違っている場合は修正してください)

  3. PHPテキストファイルへの書き込みと読み取り、特に、関数 "fopen"/"fwrite"と主に "file_put_contents"のみに関心があります。 "file_put_contents "ドキュメントですが、詳細レベルまたは" LOCK_EX "フラグが何であるか、または何であるかについての適切な説明は見つかりませんでした。

  4. このシナリオは、ファイルのサイズが大きく、データの編集方法が原因で、これらの問題が発生する可能性が高いと想定する最悪のシナリオの例です。これらの問題について詳しく知りたいのですが、「mysqlを使用する」や「なぜそうしているのか」などの回答やコメントが必要ない、または必要ないので、ファイルの読み取り/書き込みについて知りたいだけです。 PHPで、適切な場所/ドキュメントを探しているようには見えません。そうです。PHPは、この中でファイルを操作するための完璧な言語ではありません。仕方。

9
hozza

私はこれが古いことを知っていますが、誰かがこれに遭遇した場合に備えて。私見それをする方法は次のとおりです:

1)file_get_contents( 'original.txt')を使用して元のファイル(original.txtなど)を開きます。

2)変更/編集を行います。

3)file_put_contents( 'original.txt.tmp')を使用して、一時ファイルoriginal.txt.tmpに書き込みます。

4)次に、tmpファイルを元のファイルに移動し、元のファイルを置き換えます。これには、rename( 'original.txt.tmp'、 'original.txt')を使用します。

利点:ファイルの処理中およびファイルへの書き込み中はロックされず、他のユーザーは古いコンテンツを読み取ることができます。少なくともLinux/Unixボックスでは、名前の変更はアトミック操作です。ファイルの書き込み中に中断が発生しても、元のファイルには影響しません。ファイルがディスクに完全に書き込まれると、移動されます。 http://php.net/manual/en/function.rename.php へのコメントで、これについてさらにお読みください。

コメントに対応するための編集(コメントも可):

https://stackoverflow.com/questions/7054844/is-rename-atomic は、ファイルシステム全体で操作している場合に必要な可能性があることについての詳細なリファレンスがあります。

読み取り用の共有ロックでは、この実装ではファイルに直接書き込むことができないため、それが必要になる理由はわかりません。 PHPの群れ(ロックを取得するために使用される)は少しですが信頼性が低く、他のプロセスでは無視できます。そのため、名前の変更を使用することをお勧めします。

名前変更ファイルは、理想的には、2つのプロセスが同じことを行わないように、名前を変更するプロセスに一意に名前を付ける必要があります。しかし、これはもちろん、同時に複数の人が同じファイルを編集することを妨げるものではありません。ただし、少なくともファイルはそのまま残ります(最後の編集が優先されます)。

ステップ3)&4)は次のようになります。

$tempfile = uniqid(microtime(true)); // make sure we have a unique name
file_put_contents($tempFile); // write temp file
rename($tempfile, 'original.txt'); // ideally on the same filesystem
4
Dom

1)いいえ3)いいえ

元の提案されたアプローチにはいくつかの問題があります:

まず、Linuxなどの一部のUNIXライクなシステムには、ロックサポートが実装されていない場合があります。デフォルトでは、OSはファイルをロックしません。 syscallがNOP(操作なし)であることを確認しましたが、数年前なので、アプリケーションのインスタンスによって設定されたロックが別のインスタンスによって尊重されているかどうかを確認する必要があります。 (つまり、2人の同時ビジター)。ロックがまだ実装されていない場合は(可能性が高い)、OSによってそのファイルを上書きできます。

パフォーマンス上の理由から、大きなファイルを1行ずつ読み取ることはできません。 file_get_contents()を使用してファイル全体をメモリにロードし、次にexplode()して行を取得することをお勧めします。または、fread()を使用してファイルをブロック単位で読み取ります。目的は、読み取り呼び出しの数を最小限にすることです。

ファイルのロックに関して:

LOCK_EXは、排他的ロックを意味します(通常は書き込み用)。 1つのプロセスだけが、特定の時間に特定のファイルの排他ロックを保持できます。 LOCK_SHは(通常は読み取り用の)共有ロックです。複数のプロセスが、特定の時間に特定のファイルの共有ロックを保持する場合があります。 LOCK_UNはファイルのロックを解除します。ロック解除は、file_get_contents()を使用した場合に自動的に行われます http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

エレガントなソリューション

PHPは、ファイル内または他の入力からのデータを処理するためのデータストリームフィルターをサポートしています。標準のAPIを使用して、このようなフィルターを適切に作成することができます。 http://php.net/manual/en/function.stream-filter-register.phphttp://php.net/manual/en/filters.php

代替ソリューション(3ステップ):

  1. キューを作成します。 1つのファイル名を処理する代わりに、データベースまたは他のメカニズムを使用して、一意のファイル名を保留中/のどこかに保存し、/処理済みで処理します。この方法では、何も上書きされません。データベースは、メタデータ、信頼できるタイムスタンプ、処理結果などの追加情報を保存するのにも役立ちます。

  2. 数MBまでのファイルの場合は、ファイル全体をメモリに読み込んでから処理します(file_get_contents()+ explode()+ foreach())

  3. 大きなファイルの場合は、ファイルをブロック(つまり1024バイト)で読み取り、各ブロックを読み取りとしてリアルタイムで処理+書き込み(\ nで終わらない最後の行に注意してください。次のバッチで処理する必要があります)

4
user42242

PHPのドキュメント file_put_contents() のドキュメントでexample#2の使用法を見つけることができますLOCK_EXの場合、簡単に言えば:

file_put_contents('somefile.txt', 'some text', LOCK_EX);

LOCK_EXは、一部の関数で使用できるinteger値を持つ定数です- ビットごと

ファイルのロックを制御するための特定の関数もあります: flock() マナー。

1

また、注意が必要であると言及しなかった問題は、スクリプトの2つのインスタンスがほぼ同時に実行されている競合状態です。たとえば、次のような順序になります。

  1. スクリプトインスタンス1:ファイルを読み取る
  2. スクリプトインスタンス2:ファイルを読み取る
  3. スクリプトインスタンス1:変更をファイルに書き込みます
  4. スクリプトインスタンス2:ファイルへの最初のスクリプトインスタンスの変更を独自の変更で上書きします(この時点で読み取りが古くなっているため)。

したがって、大きなファイルを更新するときは、ファイルを読み取る前にそのファイルをLOCK_EXし、書き込みが行われるまでロックを解放しないようにする必要があります。この例では、2番目のスクリプトインスタンスがファイルにアクセスする順番を待つ間、2番目のスクリプトインスタンスが少しハングする原因になると思いますが、これはデータの損失よりも優れています。

0