需要:从数据库中的作业队列执行作业的 Windows 服务;通缉:示例代码

Posted

技术标签:

【中文标题】需要:从数据库中的作业队列执行作业的 Windows 服务;通缉:示例代码【英文标题】:Needed: A Windows Service That Executes Jobs from a Job Queue in a DB; Wanted: Example Code 【发布时间】:2011-03-17 00:52:30 【问题描述】:

需要:

从数据库中的作业队列执行作业的 Windows 服务

通缉:

此类应用程序的示例代码、指南或最佳实践

背景:

用户将单击 ashx 链接,该链接将在 DB 中插入一行。 我需要我的 Windows 服务定期轮询此表中的行,并且它应该为每一行执行一个工作单元。

重点:

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

【问题讨论】:

什么,您具体是在寻求建议吗? Visual Studio 包含一个用于创建 Windows 服务的项目模板。我假设因为您没有询问如何将行插入到您知道基本数据访问的数据库中。我不确定您的要求是否足够具体。 结构良好的问题。我想看到更多这些。 +1 我仍然建议 Quartz.Net 完全符合您的要求。它具有创建作业的 API,并且具有可插入的持久性机制,内置支持将作业持久化到数据库。它处理轮询,提供多种处理故障的选项,包括缺少预定时间的作业。它为您进行轮询,处理原始 CRON 表达式等。您无法使用其他组件吗? 【参考方案1】:

根据您的编辑,我能想到的一些事情:

Re:作业失败:

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

回复:争用:

添加时间戳列,例如“JobStarted”或“Locked”,以跟踪作业的启动时间。这将防止其他线程(假设您的服务是多线程的)尝试同时执行该作业。 您需要有一些清理过程来完成并清除陈旧的作业以便重新处理(如果作业服务失败并且您的锁永远不会释放)。

Re:保持服务运行

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

我真的只是在黑暗中闲逛。我强烈建议对服务进行原型设计,并返回有关其运行方式的任何具体问题。

【讨论】:

【参考方案2】:

首先你需要考虑

    多久轮询一次 您的服务是停止然后启动还是支持暂停和继续。 并发。服务可以增加遇到问题的可能性

实施

    使用 System.Timers.Timer 而不是 Threading.Timer 制造商确保将 Timer.AutoReset 设置为 false。这将停止重入问题。 确保包括执行时间

这是所有这些想法的基本框架。它包括一种调试方法,这很痛苦

        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();
    



 

EDIT 修复 Start() 中的循环

编辑原来毫秒与 TotalMilliseconds 不一样

【讨论】:

只是好奇...为什么要将 args 添加到列表中只是为了以与迭代原始数组完全相同的方式迭代列表? @Chris。 gArgs 的作用域与它所来自的代码不同。这是 2.0,所以像 args.Contains("DEBUG_SERVICE") 这样的东西对我没有用【参考方案3】:

鉴于您正在处理数据库队列,由于数据库的事务性质,您已经完成了相当一部分工作。典型的队列驱动应用程序有一个循环:

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

如果处理中途崩溃,事务将回滚,并在下一次服务启动时处理该项目。

但在数据库中编写队列实际上比您想象的要复杂得多。如果您部署一种幼稚的方法,您会发现您的入队和出队相互阻塞,并且 ashx 页面变得无响应。接下来你会发现 dequeue 与 dequeue 处于死锁状态,并且你的循环不断遇到错误 1205。我强烈建议你阅读这篇文章 Using Tables as Queues。

您的下一个挑战是让池化率“恰到好处”。过于激进,您的数据库将因池化请求而变得炙手可热。太松了,你的队列会在高峰时间增加,而且排得太慢。您应该考虑使用完全不同的方法:使用 SQL Server 内置的 QUEUE 对象并依靠 WAITFOR(RECEIVE) 语义的魔力。这允许完全免费轮询自负载调整服务行为。实际上,还有更多:您不需要服务即可开始。请参阅Asynchronous Procedures Execution 以了解我在说什么:以完全可靠的方式从 Web 服务调用在 SQL Server 中异步启动处理。最后,如果逻辑必须在 C# 进程中,那么您可以利用 External Activator,它允许将处理托管在独立进程中,而不是 T-SQL 过程。

【讨论】:

【参考方案4】:

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

【讨论】:

以上是关于需要:从数据库中的作业队列执行作业的 Windows 服务;通缉:示例代码的主要内容,如果未能解决你的问题,请参考以下文章

优先队列原理与实现

优先队列原理与实现

优先队列底层实现是堆(heap)(操作系统进程调度)

如何将链式作业分派到 Lumen 6 中的队列?

在作业中暂停队列

大数据平台 CDP 中如何配置 hive 作业的 YARN 队列以确保SLA?