ユーザーがアップロードしたファイルに基づいて一連のタスクを実行する ac# アプリケーションを開発します。これには数秒から数日かかる場合があります。プロセスが中断された場合に作業を再開するために、何らかのログ システムを実装する予定です。この中断/再開のログは何と呼ばれ、既存の実装についてどこで詳しく知ることができますか?
編集:
これまでに見つけた便利なもの:
ジャーナリングされたファイル システムは、同じ基本的な手順を使用し、いくつかの追加手順を使用します。何かのようなもの:
- ジャーナル項目: A から B へのファイルの移動
- 古いファイルを新しい場所に物理的にコピーする
- 新しいドライブのディレクトリ エントリを更新する
- 古いドライブからディレクトリ エントリを削除する
- 古いドライブの空き容量
- 仕訳入力: ファイルを A から B に移動しました
https://serverfault.com/questions/173176/what-is-a-journaling-file-systemから
チェックポイント: ジャーナル化されたメタデータとデータを固定の場所に書き込むプロセスは、チェックポイントと呼ばれます。チェックポイントは、さまざまなしきい値を超えたときにトリガーされます。たとえば、ファイル システムのバッファー スペースが少ない場合、ジャーナルに空きスペースがほとんど残っていない場合、またはタイマーが期限切れになった場合です。
クラッシュ リカバリ: ext3 でのクラッシュ リカバリは簡単です (多くのジャーナリング ファイル システムと同様)。REDO ロギングの基本的な形式が使用されます。新しい更新 (データまたはメタデータのみ) はログに書き込まれるため、ファイル システム構造をインプレースに復元するプロセスは簡単です。リカバリ中、ファイル システムはログをスキャンして、コミットされた完全なトランザクションを探します。不完全なトランザクションは破棄されます。完了したトランザクションの各更新は、単純に固定位置の ext2 構造に再生されます。
http://research.cs.wisc.edu/adsl/Publications/sba-usenix05.pdf (5 ページ)から
編集2:
この質問を最初に投稿したときよりも、耐障害性について多くのことを知っているようには感じませんが、実装したものの概要を次に示します。
ジョブ マネージャーが既存の保存された状態をファイルからロードすることを最初に試みるか、新しい空のマネージャーを作成します。
public static void Main(string[] args)
{
try
{
_jxman = JobManager<Job>.Load(Properties.Settings.Default.JobJournalFilename);
}
catch
{
_jxman = new JobManager<Job>(Properties.Settings.Default.JobJournalFilename);
}
...
_jxman.Start();
...
}
JobManager クラスは次のようになります。
public sealed class JobManager<T> : WorkGroupBase<JobBase>, IWorkSerializable where T : IJob
{
#region Fields
/// <summary>
/// Hash goes here in file
/// </summary>
private const string _hashHeading = "SHA-256";
/// <summary>
/// Flag to know whether to update the journal
/// </summary>
private bool _isDirty = false;
/// <summary>
/// Last time the journal was written to disk
/// </summary>
private DateTime _lastSaveTime = DateTime.MinValue;
/// <summary>
/// Minimum time to wait before writing journal to disk again
/// </summary>
private TimeSpan _minTimeToSave = new TimeSpan(0,0,60);
/// <summary>
/// Threading object for lock
/// </summary>
private object _lock = new object();
/// <summary>
/// Thread to monitor status
/// </summary>
private Thread _watchDirtyFlag;
#endregion
#region Properties
/// <summary>
/// journal file to track changes
/// </summary>
public string Filename
{
get;
private set;
}
#endregion
#region Constructors
/// <summary>
/// default constructor
/// </summary>
/// <param name="filename">Path to filename to write journal file</param>
public JobManager(string filename) : base()
{
ConstructorHelper();
Filename = filename;
}
/// <summary>
/// Parses XML element to recreate the item
/// </summary>
/// <param name="xe">XML element used to create object</param>
public JobManager(XElement xe)
: base(xe)
{
// Checksum validation before doing anything else.
// Will throw exception on failure.
ValidateChecksum(xe);
ConstructorHelper();
string myName = "JobManager";
XElement myself;
try
{
myself = xe.DescendantsAndSelf(myName).First();
}
catch
{
throw new ArgumentException("Attempting to instantiate object, but no relevant information was found in the XML element");
}
Filename = myself.FirstElementValue("Filename");
// Load up all the jobs
XElement[] wq = myself.Descendants("WorkQueue").Elements().ToArray();
foreach (XElement x in wq)
{
try
{
IJob blarg = (IJob)Activator.CreateInstance(typeof(T), x);
if (blarg != null)
WorkQueue.Enqueue((JobBase)blarg);
}
catch
{ }
}
}
/// <summary>
/// Helper for common constructing
/// </summary>
private void ConstructorHelper()
{
// need to wait for the base constructor to finish before attempting to
// hook events there
base.QueueChanged += new EventHandler(JobManager_QueueChanged);
base.HookQueueChangedEvents();
_watchDirtyFlag = new Thread(WatchDirtyFlag);
_watchDirtyFlag.Start();
}
#endregion
#region Methods
/// <summary>
/// Saves the state of the JobManager to Filename using XML
/// </summary>
public void Save()
{
TextWriter writer = null;
try
{
writer = new StreamWriter(Filename);
writer.Write(this.ToXElement());
}
catch (Exception ex)
{
throw ex;
}
finally
{
writer.Close();
}
}
/// <summary>
/// Loads the filename and attempts to parse it as XML to
/// create a JobManager. Pass the type of job to manage.
/// </summary>
/// <param name="filename">File storing the JobManager as XML</param>
/// <returns>JobManager with values loaded from file</returns>
public static JobManager<T> Load(string filename)
{
if (filename == "")
throw new ArgumentException("Can not load JobManager: Filename not set");
TextReader reader = null;
string text;
try
{
reader = new StreamReader(filename);
text = reader.ReadToEnd();
}
catch (Exception ex)
{
throw ex;
}
finally
{
reader.Close();
}
XElement loadFrom = null;
try
{
loadFrom = XElement.Parse(text);
}
catch //(Exception ex)
{
//throw ex;
loadFrom = new XElement("empty");
}
JobManager<T> output = new JobManager<T>(loadFrom);
output.Filename = filename;
return output;
}
/// <summary>
/// Converts the item to an XML element
/// </summary>
/// <returns></returns>
new public XElement ToXElement()
{
XElement bxe = base.ToXElement();
//string myName = this.GetType().Name;
string myName = "JobManager";
XElement wq = new XElement("WorkQueue");
foreach (IWorkSerializable t in WorkQueue.ToArray())
{
XElement addee = t.ToXElement();
wq.Add(addee);
}
bxe.Add(wq);
XElement xe = new XElement(myName,
bxe,
new XElement("Filename", Filename)
);
xe.Add(
new XElement(_hashHeading, Generic.ComputeSha256Hash(xe.ToString()))
);
return xe;
}
/// <summary>
/// Validates the checksum for the current xelement. Throws exceptions on failure
/// </summary>
/// <param name="xe">XML tree of the itme to validate</param>
private void ValidateChecksum(XElement xe)
{
XElement checksum;
try
{
checksum = xe.DescendantsAndSelf(_hashHeading).First();
}
catch (Exception ex)
{
throw new Exception("Unable to find checksum node", ex);
}
XElement withoutChecksum = new XElement(xe);
withoutChecksum.Elements(_hashHeading).Remove();
string computedChecksum = Generic.ComputeSha256Hash(withoutChecksum.ToString());
if (computedChecksum != checksum.Value)
throw new Exception("Checksum from XML element and checksum from contents of XML element do not match: \n" + xe.Value);
}
/// <summary>
/// This thread will watch the dirty flag, which is set everytime the
/// queues are changed. Every _minTimeToSave the flag is checked, and
/// if the flag is set, Save() is called.
/// </summary>
private void WatchDirtyFlag()
{
while (true)
{
// sleep until there's something to update
while (_isDirty == false)
{
Thread.Sleep(_minTimeToSave);
}
// but don't update too frequently
if (DateTime.Now.Subtract(_lastSaveTime) > _minTimeToSave)
{
// save first ...
this.Save();
// then update items ...
_lastSaveTime = DateTime.Now;
lock (_lock)
{
_isDirty = false;
}
}
}
}
#endregion
#region Event Handlers
/// <summary>
/// updates flag when any underlying queue changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void JobManager_QueueChanged(object sender, EventArgs e)
{
lock (_lock)
{
_isDirty = true;
}
}
#endregion
}
注意事項:
- 誰かがこれをコピーしようとした場合に備えて、これは不完全なコードです (基本クラスやものが欠落しています)
- 通常の (バイナリ) シリアル化と XML シリアル化はまったく正しく機能しなかったため、オブジェクトを XML として保存するカスタム シリアル化を実装しました。これがメソッドであり、引数
ToXElement()
を取るコンストラクタです。XElement
- シリアル化の最上位 (JobManager) にチェックサム (SHA-256) が含まれています。新しいオブジェクトが からインスタンス化される
XElement
と、保存されたシリアル化オブジェクトのチェックサムがファイル内のチェックサムと比較されます。 .Load(file)
ファイルを読み取って内容をデシリアライズしようとすることで、新しい JobManager オブジェクトを返す静的メソッドがあります。- カスタム ConcurrentQueue クラスはここには示されていません。これは MSDN ConcurrentQueueクラスのラッパーですが、キューが変更されたときに通知するイベントが追加されています。
- この JobManager クラスは、前述の ConcurrentQueue を持つ基本クラスを実装します。これらのキュー変更イベントは、コンストラクター ヘルパーにフックされます
- イベントが発生すると、JobManager はフラグを設定します。
_isDirty
- JobManager はインスタンス化時にスレッドを開始し、
_isDirty
フラグを監視します。ほとんどの時間はスリープに費やされますが、少なくとも_minTimeToSave
経過した場合、JobManager の内容はディスクにシリアライズされます。これにより、JobManager が頻繁にディスクに書き込むことがなくなります。
対処されていない注意事項:
- スレッドは本当に
_isDirty
フラグを監視するための正しい解決策ですか? - JobManager (単一のスレッド) は、タスクを含むジョブ (一度に 1 つずつ、ただし別のスレッド) を管理します。シリアル化中に状態をロックするためのクラスからベースクラスへの同期はありません
- 完了した古いジョブはディスクにシリアル化されてから再ロードされます