PHP file_put_contentsファイルのロック


9

セナリオ:

あなたは各行に文字列(平均文の価値)を持つファイルを持っています。議論のために、このファイルのサイズが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がこの方法でファイルを操作するための完璧な言語ではないことを理解しています。


2
経験から言うと、PHPを使用した大きなファイルの読み取りと書き込み(1 MBは実際にはそれほど大きくありませんが、それでも)はトリッキー(そして低速)になる可能性があります。ファイルはいつでもロックできますが、データベースを使用するだけの方がおそらく簡単で安全です。
NullUserException

DBを使用する方が良いでしょう。質問を読んでください(最後の段落番号4)
hozza '26 / 10/26

2
私は質問を読みました。私はそれは素晴らしいアイデアではなく、より良い代替案があると言っています。
NullUserException

2
file_put_contents()fopen()/fwrite()ダンスのラッパーにすぎず、をLOCKEX呼び出す場合と同じですflock($handle, LOCKEX)
yannis 2012年

2
@hozzaそれが私が答えではなくコメントを投稿した理由です。
NullUserException 2012年

回答:


4

1)いいえ3)いいえ

元の推奨アプローチにはいくつかの問題があります。

まず、Linuxなどの一部のUNIXライクなシステムには、ロックサポートが実装されていない場合があります。デフォルトでは、OSはファイルをロックしません。システムコールが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.php http://php.net/manual/en/filters.php

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

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

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

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


1
「syscallがNOP(操作なし)であるのを見てきました...」どのカーネルですか?
Massimo

1
「パフォーマンス上の理由から、大きなファイルを1行ずつ読み取ることはできません。file_get_contents()を使用してファイル全体をメモリにロードすることをお勧めします...」これはナンセンスです。私は言うことができます:パフォーマンス上の理由から大きなファイルをメモリに読み込まないでください...何をすべきかは他の多くの要因に依存します。
Massimo

4

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

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へのコメントでさらに興味深い内容を読んでください。

コメントに対応するために編集(コメントするには余りに):

/programming/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

まさに私が提案したいことも。しかし、私はまた、読み取り中に共有ロックを取得して、データの破壊を防止します。
d3L 2017

名前の変更は、別のディスクではなく、同じディスクでのアトミック操作です。
Xnoise

するには、本当にユニークな一時ファイル名を保証し、あなたも使用することができアトミックファイルを作成し、ファイル名を返す関数を、。tempnam
Matthijs Kooijman

1

file_put_contents()の PHPドキュメントでは、例2で簡単にLOCK_EXの使用法を見つけることができます。

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

LOCK_EXは一定である整数でいくつかの機能に使用することができるよりも値がビット単位

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


これは興味深いものであり、状況によっては役立つ場合がありますが、ファイルの読み取り、変更、再書き込みを行う場合は、ファイルを読み取る前にロックを取得し、完全に再書き込みされるまでロックを維持する必要があります(それ以外の場合、別のプロセスが古いコピーを読み取って変更する可能性があります。プロセスが終了した後)。これがで達成できるとは思いませんfile_get/put_contents
ジュール、

0

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.