4

开发 ac# 应用程序,该应用程序将根据用户上传的文件运行一系列任务;这可能需要几秒钟到几天的时间。如果进程中断,我计划实施某种日志系统以恢复工作。什么是中断/恢复日志,我在哪里可以了解有关现有实现的更多信息?

编辑:

到目前为止我发现的有用的东西:

日志文件系统将使用相同的基本过程,但需要一些额外的步骤。就像是:

  • 日记条目:将文件从 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:

与我第一次发布这个问题时相比,我觉得我对容错的了解并不多,但这里是我实现的概要。

Main 首先尝试让作业管理器从文件加载任何现有的已保存状态,或者创建一个新的空管理器。

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(单线程)管理包含任务的作业(一次一个,但不同的线程);序列化时没有类到基类同步来锁定状态
  • 旧的已完成作业被序列化到磁盘,然后重新加载
4

1 回答 1

1

我不确定是否有任何用于此目的的现有系统,但序列化是实现这种排序实现的关键。你只需要设计你的对象来支持序列化。

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

  • 为了恢复任务,您只需要反序列化对象就可以重新开始工作了。

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

  1. 什么是 [Serializable],我应该什么时候使用它?
  2. .NET 序列化(使用 BinaryFormater、SoapFormatter 和 XmlSerializer)
于 2012-11-19T16:20:54.710 回答