需要:一个能够从数据库中的作业队列执行作业的Windows服务;期望:示例代码。

6

需要:

  • 一个Windows服务,可以执行在数据库中的作业队列中的作业。

期望:

  • 关于此类应用程序的示例代码、指导或最佳实践。

背景:

  • 用户将点击ashx链接,从而向数据库中插入一行。
  • 我需要我的Windows服务定期轮询此表中的行,对每一行执行一个工作单元。

重点:

  • 这对我来说并不是完全陌生的领域。
    • 编辑:你可以假设我知道如何创建一个Windows服务和基本数据访问。
  • 但我需要从头开始编写此服务。
  • 我只想事先知道需要考虑什么。
  • 编辑:我最担心的是作业失败、作业的争用以及保持服务运行。

1
你具体需要什么建议?Visual Studio 包含用于创建 Windows 服务的项目模板。我假设你知道基本的数据访问,因为你没有询问如何将行插入数据库。我不确定你的请求是否足够具体。 - Chris
好结构化的问题。我想看到更多这样的问题。+1 - citronas
我仍然建议使用Quartz.Net,它可以完全满足您的需求。它有一个API来创建作业,并且具有可插拔的持久化机制,内置支持将作业持久化到数据库中。它处理轮询,提供了几种处理失败的选项,包括错过预定时间的作业。它为您处理轮询,处理原始CRON表达式等等。您无法使用其他组件吗? - quentin-starin
4个回答

8

考虑到您正在处理数据库队列,由于数据库的事务性质,您已经完成了相当大的工作。典型的队列驱动应用程序具有执行以下操作的循环:

while(1) {
 Start transction;
 Dequeue item from queue;
 process item;
 save new state of item;
 commit;
}

如果处理在中途崩溃,事务将回滚,并在下次服务启动时处理该项。
但是,在数据库中编写队列实际上比您想象的要棘手得多。如果您采用天真的方法,您会发现您的入队和出队互相阻塞,ashx页面变得无响应。接下来,您会发现出队与出队死锁,您的循环不断地撞上错误1205。我强烈建议您阅读这篇文章使用表作为队列
你接下来的挑战将是使池化速率“恰到好处”。如果过于激进,数据库将因池请求而变得非常繁忙。如果太松散,你的队列在高峰期将会增长并且排空速度较慢。你应该考虑使用完全不同的方法:使用SQL Server内置的QUEUE对象,并依靠WAITFOR(RECEIVE)语义的魔力。这允许完全无需轮询的自我负载调整服务行为。实际上,还有更多:你不需要一个服务来开始。请参阅异步过程执行以了解我所说的内容:从Web服务调用中异步启动处理,在SQL Server中以完全可靠的方式执行。最后,如果逻辑必须在C#进程中,则可以利用外部激活器,它允许处理托管在独立进程中,而不是T-SQL过程中。

4

首先,您需要考虑以下几点:

  1. 多久进行一次轮询
  2. 您的服务是仅支持停止和启动还是支持暂停和继续
  3. 并发性。服务可能会增加遇到问题的可能性

实现方法:

  1. 使用System.Timers.Timer而不是Threading.Timer
  2. 确保将Timer.AutoReset设置为false。这将防止可重入问题。
  3. 确保包括执行时间

这是所有这些想法的基本框架。其中包括一种繁琐的调试方式。

        public partial class Service : ServiceBase{

        System.Timers.Timer timer;


        public Service()
        {

        timer = new System.Timers.Timer();
        //When autoreset is True there are reentrancy problme 
        timer.AutoReset = false;


        timer.Elapsed += new System.Timers.ElapsedEventHandler(DoStuff);
    }


     private void DoStuff(object sender, System.Timers.ElapsedEventArgs e)
     {

        Collection stuff = GetData();
        LastChecked = DateTime.Now;

        foreach (Object item in stuff)
        {
            try
                    {
                        item.Dosomthing()
                    }
                    catch (System.Exception ex)
            {
                this.EventLog.Source = "SomeService";
                this.EventLog.WriteEntry(ex.ToString());
                this.Stop();
        }


        TimeSpan ts = DateTime.Now.Subtract(LastChecked);
        TimeSpan MaxWaitTime = TimeSpan.FromMinutes(5);


        if (MaxWaitTime.Subtract(ts).CompareTo(TimeSpan.Zero) > -1)
            timer.Interval = MaxWaitTime.Subtract(ts).TotalMilliseconds;
        else
            timer.Interval = 1;

        timer.Start();





     }

        protected override void OnPause()
     {

         base.OnPause();
         this.timer.Stop();
     }

     protected override void OnContinue()
     {
         base.OnContinue();
         this.timer.Interval = 1;
         this.timer.Start();
     }

     protected override void OnStop()
     {

         base.OnStop();
         this.timer.Stop();
     }


     protected override void OnStart(string[] args)
     {
        foreach (string arg in args)
        {
            if (arg == "DEBUG_SERVICE")
                    DebugMode();

        }

         #if DEBUG
             DebugMode();
         #endif

         timer.Interval = 1;
         timer.Start();

        }

    private static void DebugMode()
    {

        Debugger.Break();
    }



 }

编辑 修复了Start()中的循环。

编辑 原来毫秒不等同于总毫秒数。


只是好奇...为什么你要将args添加到列表中,然后以与您可以迭代原始数组相同的方式迭代该列表? - Chris
@Chris。gArgs与这段代码所在的作用域不同。这是2.0版本,所以像args.Contains <string>(“DEBUG_SERVICE”)这样的东西对我来说不可用。 - Conrad Frix

3

您可能想要查看Quartz.Net来管理作业的调度。不确定它是否适合您特定的情况,但值得一看。


1

根据您的编辑,我能想到一些解决方案:

关于作业失败:

  • 确定作业是否可以重试,并执行以下操作之一:
    • 将该行移动到一个“错误”表中,以便稍后进行日志记录/报告;或者
    • 将该行保留在队列中,以便作业服务重新处理。
    • 您可以添加一个类似于WaitUntil的列,在作业失败后延迟重试。

关于争用:

  • 添加一个时间戳列,例如“JobStarted”或“Locked”,用于跟踪作业启动的时间。这将防止其他线程(假设您的服务是多线程的)同时尝试执行该作业。
  • 您需要有一个清理过程,遍历并清除陈旧的作业以供重新处理(以防作业服务失败且锁未释放)。

关于保持服务运行:

  • 如果服务失败,您可以告诉Windows重新启动该服务。
  • 在服务运行时保持某种文件打开并在成功关闭后删除它,以便在启动时检测之前的故障。如果您的服务启动并且该文件已经存在,则知道服务以前失败,并且可以通知操作员或执行必要的清理操作。

我只是在摸索中。强烈建议先制作服务原型,然后再针对其功能提出具体问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接