开发一个c#应用程序,该应用程序将根据用户上传的文件运行一系列任务;这可能需要几秒钟到几天。我计划实施某种日志系统,以便在进程中断时恢复工作。这个用于中断/恢复的日志是什么?我在哪里可以了解有关现有实现的更多信息?
编辑:
到目前为止我发现的有用的东西:
一个记录的文件系统将使用相同的基本过程,a 几个额外的步骤。类似的东西:
- 日记帐分录:将文件从A移动到B.
- 将旧文件物理复制到新位置
- 更新新驱动器上的目录条目
- 从旧驱动器中删除目录条目
- 旧驱动器上的可用空间
- 日记帐分录:将文件从A移动到B
来自https://serverfault.com/questions/173176/what-is-a-journaling-file-system
检查点:将日记元数据和数据写入固定位置的过程称为检查点。当各种阈值触发检查点时 当文件系统缓冲区空间不足,日志中剩余可用空间很少或计时器到期时,例如交叉。
崩溃恢复:在ext3中崩溃恢复很简单(就像在许多日记文件系统中一样);使用了基本形式的重做日志。因为新的更新(是否 数据或仅元数据)被写入日志,恢复就地文件系统结构的过程很容易。在恢复期间,文件系统扫描日志以查找已提交的完成事务;不完整的交易被丢弃。完成的事务中的每个更新都被简单地重放到固定位置的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
}
注意事项:
ToXElement()
方法,以及带有XElement
参数的构造函数。XElement
实例化新对象时,将保存的序列化对象的校验和与文件中的校验和进行比较。.Load(file)
,它通过读取文件并尝试反序列化内容来返回一个新的JobManager对象。_isDirty
_isDirty
标志。大部分时间都是休眠,但如果已经过了_minTimeToSave
,则JobManager的内容会被序列化为磁盘。这应该使JobManager不会过于频繁地写入磁盘。注意到未解决的问题:
_isDirty
标志的正确解决方案吗?答案 0 :(得分:1)
我不确定任何现有的系统用于此目的,但Serialization是您可以实现此类实现的关键。您只需设计对象以支持序列化。
如果是任务中断,您可以以任何格式保存或序列化对象的状态(二进制或 XML )在文件系统上。
为了恢复任务,您只需要对对象进行反序列化,然后重新开始工作。
要了解有关序列化的更多信息,请使用以下参考: