ホームページ  >  記事  >  php教程  >  PHPでのファイルの同時読み取りと書き込みの競合の問題を解決する方法

PHPでのファイルの同時読み取りと書き込みの競合の問題を解決する方法

WBOY
WBOYオリジナル
2016-06-21 08:48:14950ブラウズ

毎日の IP が高くないか、同時実行数がそれほど多くないアプリケーションの場合は、通常、これらを考慮する必要はありません。通常のファイル操作方法では全く問題ありません。ただし、同時実行性が高い場合、ファイルの読み取りと書き込みを行うときに、その時点でファイルへのアクセスが排他的でない場合、複数のプロセスが次のファイルに対して動作する可能性が高く、データ損失が発生しやすくなります。

例: オンラインのチャット ルーム (チャットの内容がファイルに書き込まれるものとします) では、ユーザー A とユーザー B の両方が同時にデータ保存ファイルを操作する必要があります。ファイルを作成し、その中のデータを更新しますが、ここで B も同じファイルを開いていて、その中のデータを更新する準備をしています。 A が書き込んだファイルを保存すると、実際には B がそのファイルを開いていることになります。しかし、B がファイルを保存し直すと、すでにデータ損失が発生しています。ユーザー B は、変更したときに開いたファイルを知らないため、ユーザー A もファイルを変更したため、最終的にユーザー B が変更を保存するときに、ユーザーA の更新は失われます。

このような問題に対する一般的な解決策は、あるプロセスがファイルを操作するときに、最初に他のプロセスをロックすることです。つまり、他のプロセスがそのファイルを読み取る権限を持つのはこのプロセスだけになります。まったく問題ありませんが、この時点でプロセスがファイルを更新しようとすると、以前にファイルをロックしていたプロセスがファイルの更新操作を完了すると、その操作は拒否されます。今度は、ファイルが変更可能な状態に復元されます。次に、同様に、プロセスがファイルを操作するときにファイルがロックされていない場合は、ファイルを安全にロックして単独で楽しむことができます。

一般的な解決策は次のようになります:

$fp=fopen('/tmp/lock.txt','w+');
if (flock($fp,LOCK_EX)){
    fwrite($fp,"Write something here\n");
    flock($fp,LOCK_UN);
}else{
    echo 'Couldn\'t lock the file !';
}
fclose($fp);

しかし、PHP では flock はあまりうまく機能しないようです。複数の同時実行の場合、リソースが独占されてすぐに解放されなかったり、まったく解放されなかったりしてデッドロックが発生し、サーバーの CPU 使用率が非常に高くなり、場合によってはサーバーが完全に停止してしまうこともあるようです。これは多くの Linux/UNIX システムで発生するようです。したがって、flock を使用する前に慎重に検討する必要があります。

それでは解決策はないのでしょうか?実際にはそうではありません。 flock() を適切に使用すれば、デッドロックの問題を解決することは完全に可能です。もちろん、 flock() 関数の使用を考慮しない場合でも、問題に対する適切な解決策はあります。私個人の収集と要約を経て、解決策は大まかに次のようにまとめられます。

オプション 1: ファイルをロックするときにタイムアウトを設定します。おおよその実装は次のとおりです:

if($fp=fopen($fileName,'a')){
	$startTime=microtime();
	do{
		$canWrite=flock($fp,LOCK_EX);
		if(!$canWrite){
			usleep(round(rand(0,100)*1000));
		}
	}while((!$canWrite)&&((microtime()-$startTime)<1000));
	if($canWrite){
		fwrite($fp,$dataToSave);
	}
	fclose($fp);
}

タイムアウトは1msに設定されており、この時間内にロックが取得できなかった場合は、当然ながらファイルの操作権が得られるまで繰り返しロックが取得されます。タイムアウト制限に達した場合は、ただちに終了し、他のプロセスが動作するようにロックを放棄する必要があります。
解決策 2: flock 関数を使用せず、一時ファイルを使用して読み取りと書き込みの競合の問題を解決します。一般原則は次のとおりです:

(1) 更新する必要があるファイルのコピーを一時ファイル ディレクトリに置き、ファイルの最終変更時刻を変数に保存し、この一時ファイルにランダムで反復不可能なファイルを与えます。名前 。

(2) この一時ファイルを更新した後、元のファイルの最終更新時刻が以前に保存した時刻と一致しているかどうかを確認します。

(3) 最終変更時刻が同じ場合、変更された一時ファイルの名前を元のファイルに変更します。ファイルのステータスが同期して更新されるようにするには、ファイルのステータスをクリアする必要があります。

(4) ただし、最終変更時刻が以前に保存された時刻と一致する場合は、この期間中に元のファイルが変更されたことを意味するため、この時点で一時ファイルを削除する必要があります。その後 false を返し、ファイルが現在他のプロセスによって操作されていることを示します。

おおよその実装コードは次のとおりです:

$dir_fileopen='tmp';
function randomid(){
    return time().substr(md5(microtime()),0,rand(5,12));
}
function cfopen($filename,$mode){
    global $dir_fileopen;
    clearstatcache();
    do{
		$id=md5(randomid(rand(),TRUE));
        $tempfilename=$dir_fileopen.'/'.$id.md5($filename);
    } while(file_exists($tempfilename));
    if(file_exists($filename)){
        $newfile=false;
        copy($filename,$tempfilename);
    }else{
        $newfile=true;
    }
    $fp=fopen($tempfilename,$mode);
    return $fp?array($fp,$filename,$id,@filemtime($filename)):false;
}
function cfwrite($fp,$string){
	return fwrite($fp[0],$string);
}
function cfclose($fp,$debug='off'){
    global $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){
        rename($tempfilename,$fp[1]);
    }else{
        unlink($tempfilename);
		//说明有其它进程 在操作目标文件,当前进程被拒绝
        $success=false;
    }
    return $success;
}
$fp=cfopen('lock.txt','a+');
cfwrite($fp,"welcome to beijing.\n");
fclose($fp,'on');

上記のコードで使用されている関数について説明する必要があります:

(1) rename(); ファイルまたはディレクトリの名前を変更するこの関数は、実際には Linux の mv に似ています。ファイルやディレクトリのパスや名前を更新すると便利です。ただし、ウィンドウで上記のコードをテストすると、新しいファイル名がすでに存在する場合、現在のファイルがすでに存在するという通知が表示されます。ただし、Linux では正常に動作します。

(2) clearstatcache(); ファイルのステータスをクリアします。PHP はパフォーマンスを向上させるためにすべてのファイル属性情報をキャッシュしますが、複数のプロセスがファイルを削除または更新する場合、PHP はキャッシュしません。キャッシュ内のファイル属性を更新する時間は、実際のデータではない最終更新時間に簡単にアクセスできます。したがって、ここではこの関数を使用して保存されたキャッシュをクリアする必要があります。

オプション 3: 同時実行の可能性を減らすために、操作されたファイルをランダムに読み書きします。

このソリューションは、ユーザーのアクセス ログを記録するときによく使用されるようです。以前は、ランダムなスペースを定義する必要がありましたが、スペースが大きいほど、同時実行の可能性は低くなり、ランダムな読み取りおよび書き込みスペースが [1 ~ 500] であると仮定すると、ログ ファイルの分布範囲は log1 から log500 になります。ユーザーがアクセスするたびに、log1 ~ log500 までの任意のファイルにデータがランダムに書き込まれます。同時に、2 つのプロセスがログを記録しています。プロセス A は更新された log32 ファイルである可能性がありますが、プロセス B はどうなるでしょうか。このときの更新は log399 になる可能性があります。プロセス B にも log32 を実行させたい場合、確率は基本的に 1/500 であり、ほぼゼロに等しいことを知っておく必要があります。アクセスログを分析する必要がある場合、これらのログをマージしてから分析するだけで済みます。このソリューションを使用してログを記録する利点の 1 つは、プロセス操作がキューに入れられる可能性が比較的小さく、プロセスが各操作を非常に迅速に完了できることです。

オプション 4: 操作するすべてのプロセスをキューに入れます。次に、ファイル操作を完了するための専用サービスを配置します。キュー内の除外された各プロセスは最初の特定の操作に相当するため、初めて、サービスはキューから特定の操作アイテムを取得するだけで済みます。ここに多数のファイル操作プロセスがある場合でも、それは問題ではありません。列の後ろに並んでください。列に並ぶ意思がある限り、列の長さは関係ありません。

前述のオプションについては、それぞれに独自の利点があります。大きく分けて

の2つに分類できます。

(1) オプション 1、2、4 などのキューに入れる必要がある (影響は遅い)

(2) 並ぶ必要はありません。 (ファストインパクト) オプション 3

キャッシュ システムを設計する場合、通常、オプション 3 は採用しません。案3は解析プログラムと書き込みプログラムが同期していないため、書き込みの際に解析の難易度は全く考慮されず、書き込みがよければ問題ありません。想像してみてください。キャッシュの更新時にランダムなファイルの読み書きも併用すると、キャッシュの読み込み時に多くの処理が追加されそうです。ただし、オプション 1 と 2 はまったく異なりますが、書き込み時間はかかりますが (ロックの取得に失敗した場合は繰り返し取得されます)、ファイルの読み取りは非常に便利です。キャッシュを追加する目的は、データ読み取りのボトルネックを軽減し、それによってシステムのパフォーマンスを向上させることです。

上記は個人的な経験といくつかの情報の要約です。間違っている点や言及されていない点がある場合は、同僚が訂正してください。

興味のある記事

  • PHP の GZip 圧縮機能を使用して Web サイトの JS および CSS ファイルを圧縮し、Web サイトへのアクセスを高速化します
  • php の抽象クラスと抽象メソッドに関する問題の分析
  • php はどのようにして html 形式をクリアし、テキスト内のスペースを削除し、テキストをインターセプトしますか
  • php はファイルをクリア (削除) します指定したディレクトリ ファイル、ディレクトリ フォルダを削除しないメソッド
  • ディレクトリ内のすべてのファイルを取得し、結果を配列に保存する php プログラム
  • 変数をシリアル化する PHP メソッド、シリアル化された 4 つの変数の競合php メソッド
  • PHP 関数memory_get_usageを使用して、現在のPHPメモリ消費量を取得し、プログラムのパフォーマンスを最適化します
  • php error_log() エラー情報をファイルに書き込みます



声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。