4

フォローアップ多くのリーダーと1人のライターがいるファイルを安全に更新するにはどうすればよいですか?

以前の質問で、FileChannelのロックを使用して、読み取りと書き込みの順序を確認できることがわかりました。

しかし、ライターが書き込みの途中で失敗した場合(たとえば、JVMがクラッシュした場合)、どのように対処しますか?この基本的なアルゴリズムは次のようになります。

WRITER:
  lock file
  write file
  release file

READER:
  lock file
  read file
  release file

中にJVMがクラッシュした場合はwrite file、ロックが解除されることを確認してください。ただし、ファイルが不完全になっています。完全なものを常に読みやすくしたい。古いコンテンツと新しいコンテンツのどちらかであり、その間には何もありません。

私の最初の戦略は、一時ファイルに書き込んでから、内容を「ライブ」ファイルにコピーすることでした(適切なロックを確保しながら)。このためのアルゴリズムは、

WRITER:
  lock temp file
  write temp file
  lock file
  copy temp to file
  release file
  release temp
  delete temp

READER:
  lock file
  read file
  release file

良い点の1つは、delete temp別のライターによって既にロックされている場合、一時を削除しないことです。

ただし、の間にJVMがクラッシュした場合、そのアルゴリズムは処理しませんcopy temp to file。それで私はcopying旗を追加しました、

WRITER:
  lock temp file
  write temp file
  lock file
  create copying flag
  copy temp to file
  delete copying flag
  release file
  release temp
  delete temp

READER:
  lock file
  if copying flag exists
    copy temp to file
    delete copying flag
    delete temp 
  end
  read file
  release file

copyingファイルはファイルロックによって保護されているため、ファイルにアクセスすることは2つありません。

さて、これはそれを行う方法ですか?非常に単純なものを確保することは非常に複雑に思えます。これを処理するJavaライブラリはありますか?

編集

さて、私は3回目の試みで間違いを犯すことができました。リーダーは、温度を上げるときにロックを保持しませんcopy temp to file。また、一時ファイルをロックするだけの簡単な修正ではありません。これにより、ライターとリーダーが異なる順序でロックを取得し、デッドロックが発生する可能性があります。これは常に複雑になっています。これが私の4回目の試みです。

WRITER:
  lock file
  write temp file
  create copying flag
  copy temp to file
  delete copying flag
  delete temp
  release file

READER:
  lock file
  if copying flag exists
    copy temp to file
    delete copying flag
    delete temp 
  end
  read file
  release file

今回は一時ファイルがメインロックで保護されているため、独自のロックも必要ありません。

編集2

私がJVMクラッシュと言うとき、私は実際に電源が切れて、UPSを持っていなかったと言うことを意味します。

編集3

私はまだ別の間違いをすることができました。書き込みまたは読み取りを行っているファイルをロックしないでください。入出力ストリームを実装していないJavaでRandomAccessFileを使用しない限り、読み取りロックと書き込みロックの両方を取得できないため、これにより問題が発生します。

代わりに実行したいのは、読み取りまたは書き込み中のファイルを保護するロックファイルをロックすることです。更新されたアルゴリズムは次のとおりです。

WRITER:
  lock
  write temp file
  create copying flag
  copy temp to file
  delete copying flag
  delete temp
  release

READER:
  lock
  if copying flag exists
    copy temp to file
    delete copying flag
    delete temp 
  end
  read file
  release

ロックと解放は、ファイル、一時ファイル、およびコピーフラグを保護します。唯一の問題は、リーダーロックを共有できないことですが、実際には共有できません。読者は常にファイルを変更する機会があったので、そもそも共有可能なロックを作成するのは間違っていたでしょう。

4

5 に答える 5

1

防弾、クロス OS、クロス FS ソリューションがないという事実にもかかわらず、「一意の一時ファイルに書き込み、名前を変更する」戦略は依然として最良の選択肢です。ほとんどのプラットフォーム/ファイルシステムは、ファイルの名前変更を (効果的に) アトミックにしようとします。ロックには+別の+ロックファイルを使用することに注意してください。

したがって、「myfile.txt」を更新するとします。

  • lock "myfile.txt.lck" (これは別の空のファイルです)
  • 「myfile.txt.13424.tmp」などの一意の一時ファイルに変更を書き込みます (使用File.createTempFile())
  • 保護を強化するために、処理が遅くなる可能性がありますが、続行する前に一時ファイルを fsync します ( FileChannel.force(true))。
  • 「myfile.txt.13424.tmp」の名前を「myfile.txt」に変更します
  • 「myfile.txt.lck」のロックを解除

特定のプラットフォーム (Windows) では、ファイル操作の制限により、もう少し複雑にする必要があります (名前を変更する前に「myfile.txt」を「myfile.txt.old」に移動し、「.old」ファイルを使用して回復することができます)。読むときに必要な場合から)。

于 2011-04-04T17:26:57.073 に答える
0

継続的に追加している大きなファイルがあると思います。VMのクラッシュはあまり発生しません。ただし、それらが発生した場合は、失敗した変更をロールバックする方法が必要です。ロールバックする距離を知る方法が必要です。たとえば、最後のファイルの長さを新しいファイルに書き込むことによって:

WRITER:
  lock file
  write file position to pos-file
  write file
  remove pos-file
  unlock file

ライターがクラッシュすると、リーダーの1つが読み取りロックを取得します。彼らはpos-fileをチェックする必要があります。彼らが1つを見つけた場合、クラッシュが発生しました。彼らがファイルの内部を見ると、変更をどこまでロールバックするかがわかり、一貫性のあるファイルを再度取得できます。もちろん、ロールバック手順は書き込み手順と同様の方法で実行する必要があります。

ファイルを追加せずに置き換える場合は、同じ方法を使用できます。

WRITER:
  lock file
  write writing-in-progress-file
  write file
  remove writing-in-progress-file
  unlock file

以前と同じルールがリーダーに適用されます。書き込み中のファイルが存在するが、リーダーがすでに読み取りロックを取得している場合、書き込まれたファイルは不整合な状態になります。

于 2009-01-20T22:09:00.137 に答える
0

解決策: 2 つのファイルを使用して、連続した順序で書き込みます。
失敗する可能性のある書き込みは 1 つだけです。

于 2011-05-18T13:39:32.053 に答える
0

完璧な答えはないと思います。何をする必要があるのか​​ 正確にはわかりませんが、新しいファイルへの書き込みを行い、成功したらコピーではなくファイルの名前を変更できますか. 名前の変更は迅速であるため、クラッシュする可能性が低くなります。名前の変更段階で失敗した場合、これはまだ役に立ちませんが、リスクのウィンドウを最小限に抑えました.

繰り返しますが、それがあなたのニーズに当てはまるかどうかはわかりませんが、ファイルの最後にファイルブロックを書いて、すべてのデータが書き込まれたことを示すことができますか?

于 2009-01-20T19:39:46.600 に答える
0

何らかのオペレーティング システムのサポートがなければ、ファイルを開くプログラムが進行中の操作を完了またはロールバックする必要がなければ、任意に複雑なファイル操作をアトミックにすることはできません。その警告が受け入れられる場合は、次のようなことができます。

  1. ファイルの先頭近くで、512 バイトの境界にまたがらないように、ファイルの論理長と更新レコード (ある場合) の場所を示す 2 つの 4 または 8 バイトの数字 (最大ファイル サイズに応じて異なる) を含めます。 . ほとんどの場合、更新レコードの値はゼロになります。ゼロ以外の値を書き込む行為は、更新シーケンスをコミットします。更新シーケンスが完了すると、ゼロで書き換えられます。
  2. 更新シーケンスを開始する前に、更新シーケンスが完了したときのファイルの論理的な長さの上限を決定します (この制限を回避する方法はいくつかありますが、複雑さが増します)。
  3. 更新シーケンスを開始するには、現在の論理長または将来の論理長 (の上限) の大きい方の距離をファイルにシークし、更新レコードを書き込みます。更新レコードはそれぞれファイル オフセット、バイト数で構成されます。書き込み、書き込みデータ。すべての更新を実行するために必要な数のレコードを書き込み、オフセットが 0 で長さが 0 のレコードで終了します。
  4. 更新シーケンスをコミットするには、保留中のすべての書き込みをフラッシュし、それらの完了を待ってから、新しいファイルの長さと最初の更新レコードの場所を書き込みます。
  5. 最後に、すべての更新レコードを順番に処理し、すべての書き込みをフラッシュして完了を待ち、更新レコードの場所をゼロに設定し、(オプションで) ファイルをその論理長に切り詰めることによって、ファイルを更新します。
  6. update-sequence の場所が 0 以外のファイルを開こうとした場合は、保留中の書き込みの実行を (上記の最後の手順を使用して) 終了してから、他の処理を実行してください。

更新レコードの場所が書き込まれる前にファイルを書き込む元の操作が失敗した場合、すべての書き込み操作は事実上無視されます。更新レコードの場所が書き込まれた後、クリアされる前に失敗した場合、次にファイルが開かれたときに、すべての書き込み操作がコミットされます (一部は既に実行されている可能性がありますが、再度実行しても問題はありません)。 . 更新レコードの場所が書き込まれた後に失敗した場合、ファイルの更新は完了し、失敗はまったく影響しません。

他のいくつかのアプローチでは、別のファイルを使用して保留中の書き込みを保持します。場合によっては、それが良い考えかもしれませんが、ファイルが 2 つの部分に分割され、両方を一緒に保管しなければならないという欠点があります。1 つのファイルだけをコピーしたり、異なる時期に作成された 2 つのファイルのコピーを誤って組み合わせたりすると、データの損失や破損が発生する可能性があります。

于 2011-04-04T16:26:16.590 に答える