类似于文件系统日志记录或容错日志记录来处理任务的过程?

时间:2012-11-19 14:44:40

标签: c# error-handling journaling

开发一个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
}

注意事项:

  • 这是不完整的代码,以防有人试图复制它(缺少基类和东西)
  • 常规(二进制)和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 个答案:

答案 0 :(得分:1)

我不确定任何现有的系统用于此目的,但Serialization是您可以实现此类实现的关键。您只需设计对象以支持序列化。

  • 如果是任务中断,您可以以任何格式保存或序列化对象的状态(二进制 XML )在文件系统上。

  • 为了恢复任务,您只需要对对象进行反序列化,然后重新开始工作。

要了解有关序列化的更多信息,请使用以下参考:

  1. What is [Serializable] and when should I use it?
  2. .NET Serialization (using BinaryFormater, SoapFormatter and XmlSerializer)