ホームページ >バックエンド開発 >PHPチュートリアル >PHP_PHP チュートリアルでファイルの同時読み取りと書き込みの競合の問題を解決する方法
日次 IP が高くない、または同時実行数がそれほど多くないアプリケーションの場合、通常はこれらを考慮する必要はありません。通常のファイル操作方法ではまったく問題ありません。しかし、同時実行性が高い場合、ファイルの読み取りと書き込みを行うときに、ファイルへのアクセスがそれに応じて独占されないと、複数のプロセスが次のファイルに対して動作する可能性が高くなります。
例:オンラインチャットルーム(ここではチャット内容がファイルに書き込まれると仮定します) 同時に、ユーザーAとユーザーBの両方がデータ保存ファイルを操作する必要があります。次に、その中のデータを更新しますが、ここでは、B たまたま同じファイルが開かれ、その中のデータが更新されようとしていたところです。 A が書き込んだファイルを保存すると、実際には B がそのファイルを開いていることになります。しかし、B がファイルを保存し直すと、すでにデータ損失が発生しています。ユーザー B は、変更したときに開いたファイルを知らないため、ユーザー A もファイルを変更したため、最終的にユーザー B が変更を保存するときに、ユーザーA の更新は失われます。
このような問題に対する一般的な解決策は、あるプロセスがファイルを操作するときに、最初に他のプロセスをロックすることです。つまり、このプロセスだけがそのファイルを読み取る権利を持ち、他のプロセスがそのファイルを読み取った場合、他のプロセスはそのファイルを読み取ることはできなくなります。まったく読み取ることはできませんが、この時点でプロセスがファイルを更新しようとすると、以前にファイルをロックしていたプロセスがファイルの更新操作を完了すると、その操作は拒否されます。 、ファイルは変更可能な状態に復元されます。次に、同様に、プロセスがファイルを操作するときにファイルがロックされていない場合は、ファイルを安全にロックして単独で楽しむことができます。
一般的な計画は次のとおりです:
$fp=fopen('/tmp/lock.txt','w+');
if (flock($fp,LOCK_EX)){
fwrite($fp,"ここに何かを書いてください");
flock($fp,LOCK_UN);
}その他{
echo 'ファイルをロックできませんでした!';
}
fclose($fp);
しかし、PHP では、flock はそれほどうまく機能しないようです。複数の同時実行状況では、リソースが独占されてすぐに解放されないか、まったく解放されず、デッドロックが発生し、サーバーの CPU 使用率が低下することが多いようです。非常に高い値になるか、場合によってはサーバーが完全に停止する場合もあります。これは多くの Linux/UNIX システムで発生するようです。したがって、flock を使用する前に慎重に検討する必要があります。
では、解決策はないのですか、実際にはそうではありません。 flock() を適切に使用すれば、デッドロックの問題を解決することは完全に可能です。もちろん、flock() 関数の使用を考慮しない場合でも、問題に対する適切な解決策はあります。私個人の収集と要約を経て、解決策は大まかに次のようにまとめられます。
オプション 1: ファイルをロックするときにタイムアウトを設定します。おおよその実装は次のとおりです:
if($fp=fopen($fileName,'a')){
$startTime=microtime();
する{
$canWrite=flock($fp,LOCK_EX);
if(!$canWrite){
usleep(round(rand(0,100)*1000));
}
}while((!$canWrite)&&((microtime()-$startTime)
if($canWrite){
fwrite($fp,$dataToSave);
}
fclose($fp);
}
タイムアウトは1msに設定されており、この時間内にロックが取得できなかった場合は、当然ファイルの操作権を取得するまで繰り返しロックが取得されます。タイムアウト制限に達した場合は、ただちに終了してロックを放棄し、他のプロセスが動作できるようにする必要があります。
オプション 2: flock 関数を使用する代わりに、一時ファイルを使用して読み取りと書き込みの競合の問題を解決します。一般原則は次のとおりです:
(1) 更新する必要があるファイルのコピーを一時ファイルディレクトリに置き、ファイルの最終変更時刻を変数に保存し、この一時ファイルに繰り返しにくいランダムなファイル名を付けます。
(2) この一時ファイルを更新した後、元のファイルの最終更新時刻が以前に保存した時刻と一致しているかどうかを確認します。
(3) 最終変更時刻が同じ場合、変更された一時ファイルの名前を元のファイルに変更します。ファイルのステータスが同期的に更新されるようにするには、ファイルのステータスをクリアする必要があります。
(4) ただし、最終変更時刻が以前に保存された時刻と一致する場合は、この期間中に元のファイルが変更されたことを意味し、その時点で一時ファイルを削除する必要があり、それを示す false を返します。現時点では、ファイルには他の変更が加えられています。プロセスは実行中です。
おおよその実装コードは次のとおりです:
$dir_fileopen='tmp';
関数randomid(){
return time().substr(md5(microtime()),0,rand(5,12));
}
関数 cfopen($filename,$mode){
グローバル $dir_fileopen;
clearstatcache();
する{
$id=md5(randomid(rand(),TRUE));
$tempfilename=$dir_fileopen.'/'.$id.md5($filename);
} while(file_exists($tempfilename));
if(file_exists($filename)){
$newfile=false;
copy($filename,$tempfilename);
}その他{
$newfile=true;
}
$fp=fopen($tempfilename,$mode);
return $fp?array($fp,$filename,$id,@filemtime($filename)):false;
}
関数 cfwrite($fp,$string){
return fwrite($fp[0],$string);
}
関数 cfclose($fp,$debug='off'){
グローバル $dir_fileopen;
$success=fclose($fp[0]);
clearstatcache();
$tempfilename=$dir_fileopen.'/'.$fp[2].md5($fp[1]);
if((@filemtime($fp[1])==$fp[3])($fp[4]==true&&!file_exists($fp[1]))$fp[5]==true){
名前を変更($tempfilename,$fp[1]);
}その他{
リンク解除($tempfilename);
//対象ファイルを操作している他のプロセスがあり、現在のプロセスが拒否されたことを示します
$success=false;
}
$success を返す;
}
$fp=cfopen('lock.txt','a+');
cfwrite($fp,"ようこそ北京へ.n");
fclose($fp,'on');
上記のコードで使用されている関数については、次の説明が必要です:
(1)rename(); この関数は実際には Linux の mv に似ています。ファイルやディレクトリのパスや名前を更新すると便利です。ただし、ウィンドウで上記のコードをテストすると、新しいファイル名がすでに存在する場合、現在のファイルがすでに存在するという通知が表示されます。ただし、Linux では正常に動作します。
(2) clearstatcache(); ファイルのステータスをクリアします。PHP はパフォーマンスを向上させるためにすべてのファイル属性情報をキャッシュしますが、複数のプロセスがファイルを削除または更新するときに、PHP がキャッシュを更新する時間がない場合があります。実際のデータではない最終更新時刻に簡単にアクセスしてしまう可能性があります。したがって、ここではこの関数を使用して保存されたキャッシュをクリアする必要があります。
オプション 3: 同時実行の可能性を減らすために、操作されたファイルをランダムに読み書きします。
ユーザーのアクセスログを記録する場合には、このソリューションが使用されることが多いようです。以前は、ランダムなスペースを定義する必要がありました。スペースが大きいほど、同時実行の可能性は低くなります。ランダムな読み取りおよび書き込みスペースが [1 ~ 500] であるとすると、ログ ファイルの分布は log1 から log500 の範囲になります。ユーザーがアクセスするたびに、log1 ~ log500 までの任意のファイルにデータがランダムに書き込まれます。同時に、2 つのプロセスがログを記録しています。プロセス A は更新された log32 ファイルである可能性がありますが、プロセス B も log32 を操作したい場合は、この時点での更新は log399 である可能性があることを知っておく必要があります。 , 確率は基本的に上限は1/500でほぼゼロに等しいです。アクセスログを分析する必要がある場合、ここでは、最初にこれらのログをマージしてから分析するだけです。このソリューションを使用してログを記録する利点の 1 つは、プロセス操作がキューに入れられる可能性が比較的小さく、プロセスが各操作を非常に迅速に完了できることです。
オプション 4: 操作するすべてのプロセスをキューに入れます。次に、ファイル操作を完了するための専用サービスを配置します。キュー内の除外された各プロセスは最初の特定の操作に相当するため、ここに多数のファイル操作プロセスがある場合、初めてサービスはキューから特定の操作アイテムを取得するだけで済みます。列の最後尾に並んでください。列に並ぶ意思がある限り、列の長さは関係ありません。
上記のソリューションには、それぞれ独自の利点があります:
(1) オプション 1、2、4 などのキューに入れる必要がある (遅い影響)
(2)並ぶ必要はありません。 (ファストインパクト) オプション 3
キャッシュ システムを設計する場合、通常、オプション 3 は採用されません。案3は解析プログラムと書き込みプログラムが同期していないため、書き込みの際に解析の難易度は全く考慮されず、書き込みがよければ問題ありません。想像してみてください。キャッシュの更新時にランダムなファイルの読み書きも併用すると、キャッシュの読み込み時に多くの処理が追加されそうです。ただし、オプション 1 と 2 はまったく異なりますが、書き込み時間はかかりますが (ロックの取得に失敗した場合は繰り返し取得されます)、ファイルの読み取りは非常に便利です。キャッシュを追加する目的は、データ読み取りのボトルネックを軽減し、それによってシステムのパフォーマンスを向上させることです。
上記は個人的な経験といくつかの情報の要約です。間違っている点や言及されていない点がある場合は、同僚が私を修正することを歓迎します。