「アクセスカウンタが0になってる!」 こんなことが起こったら大変です。多くの場合、原因はアクセス集中によるデータファイルの書込み重複破壊が考えられます。これを防ぐにはファイル書込み中に他のアクセスを蹴るもしくは待たせれば良く、これをファイルロック(排他制御)と言います。
ファイルロックには主に以下のような方法が考えられています。
|
1.flockを使う |
|
flock はファイルロックをする関数です。Perl自身がロックしてくれます。 |
2.symlinkを利用する |
|
symlink シンボリックリンク(任意のファイルに対して別な名前で関連つける機能)を行う関数で、その可否を利用して排他制御を行います。 |
3.ファイルにフラグを書き込む |
|
排他制御用ファイルを用意し、ロック時に1を解除時に0などのフラグを設定します。 |
4.mkdirを利用する |
|
排他制御用にフラグの変わりにmkdirでディレクトリを作成し、その有無を利用します。 |
5.renameを利用する |
|
データファイルをrenameで変更し、データファイル自身の有無を利用します。 |
上記の方法を検証すると、まず 1 と 2 の方法は使用する関数自身がプラットホームに依存しサポートされていない場合があり汎用性という面で問題がありそうです。 3 の方法は一見問題なさそうですが排他制御用ファイル自身も書込みが発生するので、それ自身のロックはどうするのでしょうか? と言うことで 4 と 5 の方法が良さそうです。
次に、異常終了時の処理について考えます。
mkdir や rename を使用した場合CGIプログラムがファイルロック中に落ちた場合、ロック状態が解除されず以降にやってきた人(?)は常に蹴られるか最悪の場合ずっと待たされることになります。そこで、ある程度の時間待ちで異常ロック状態と判断してロックを解除してやらなければなりません。
mkdir の場合はディレクトリを削除、renameの場合は元のファイル名に直せば異常なロック状態は簡単に解除できます。 ...が以下のような3つ以上のアクセスがあった場合を考えてください!
|
アクセス@ |
|
異常検知 |
異常解除 |
→ |
→ |
→ |
アクセスA |
異常検知 |
→ |
→ |
異常解除 |
→ |
アクセスB |
- |
- |
ロック |
→ |
解除? |
|
上記の場合、@が異常ロック解除したため Bはロック可能です。しかし、Aも異常を検知していたため Bの正常なロックを解除してしまいます。したがって、Bはロックしたつもりが非ロック状態で今後の作業を行うことになってしまいます!
では、どうすれば Aの誤った異常解除を防げるのでしょうか? それは異常検知した時のロックと異常解除を実行する時のロックが同じもの、つまりロックに札がついていれば良いのです。
また、@は異常解除後に正常なロックを行うはずですが、異常解除と新たなロックを行う際に隙間があると
Bはロック状態を検知できず @ と B は互いに知らぬ顔してデータファイルへ書き込む可能性もあります。
以上の検証から、札付きのロック状態が実現でき、かつ、異常解除と新たなロック状態を隙間なく行える第6の方法が必要になります。
// ファイルロック関数
sub fileLock {
// ロック情報を定義
my %lfi = (dir => './lockdir/', lockname => @_[0], timeout
=> 60, trytime => 10);
$lfi{base} = $lfi{dir} . $lfi{lockname};
// ファイルロックの試行
// 他でロックされていればロック用ファイルが存在しないのでrenameが失敗しロック中であることがわかる
// result で付加される time は異常ロックのタイムアウトと札の意味がある
for (my $i = 0; $i < $lfi{trytime}; $i++, sleep 1) {
return \%lfi if (rename($lfi{base}, $lfi{result} = $lfi{base} . time));
}
// 異常ロックのチェック
// ロック用ファイルのディレクトリ内を走査し、タイムアウトをチェック
// 異常ロックファイルを新たなロックファイルに直接renameし、隙間なく移行している
opendir(LOCKDIR, $lfi{dir});
my @filelist = readdir(LOCKDIR);
closedir(LOCKDIR);
foreach (@filelist) {
if (/^$lfi{lockname}(\d+)/) {
return \%lfi if (time - $1 > $lfi{timeout}
&& rename($lfi{dir} . $_, $lfi{result} = $lfi{base} . time));
last;
}
}
// ロック中に付きロック情報は返さない
undef;
}
// ファイルロック解除関数
sub fileUnlock {
rename($_[0]->{result}, $_[0]->{base});
}
|
このようなスクリプトなら、より安全にファイルロックを行えるのではないでしょうか。
☆ 具体的な導入方法は以下の通りです。
|
下準備. |
|
ロックを格納する為のディレクトリ(lockdir)とロック用ファイルをアップロードする。
ロック用ファイルは空ファイルでファイル名に拡張子はつけない。ロックしようとするデータファイルから拡張子を除いた名前にした方が後でメンテナンスがしやすい。 |
1. |
ファイルロックをする。
$lfi = &fileLock("ロック用ファイル名") || die 'ビジー!'; |
2. |
書込みなどの諸作業を行う。
open(OUT, ">データファイル名"); (中略) close(OUT); |
3. |
ファイルロックを解除する。
&fileUnlock($lfi); |
|