当站点位于多台服务器上时,每分钟运行一次自动化任务的最佳方式

本文关键字:一次 自动化 任务 方式 最佳 运行 于多台 站点 服务器 每分钟 | 更新日期: 2023-09-27 17:57:31

我需要设置一个每分钟运行一次并在队列中发送电子邮件的自动化任务。我正在使用ASP。NET 4.5和C#。目前,我使用一个从global.asax中启动的调度器类,它利用了缓存和缓存回调。我读过这篇文章,它引出了几个问题。

我这样做的原因是,这个应用程序在多个负载平衡的服务器上运行,这允许我在一个地方执行,即使一个或多个服务器离线,代码也会运行。

我正在寻找一些方向来让这件事变得更好。我读过关于Quartz的文章。NET,但从未使用过。Quartz。NET调用方法?还是从windows服务?还是从网络服务?

我也读过关于使用Windows服务的文章,但据我所知,这些服务是直接安装到服务器上的。问题是,无论有多少台服务器在线,我都需要执行任务,并且不想重复它。例如,如果我在服务器1和服务器2上设置了一个计划任务,它们都会一起运行,因此会重复请求。但是,如果服务器1处于脱机状态,我需要服务器2来运行该任务。

关于如何在这里前进,或者global.asax方法是多服务器环境的最佳方法,有什么建议吗?顺便说一句,web服务器正在运行带有IIS 8的Win Server 2012。

编辑

在请求更多信息时,队列存储在数据库中。我还应该提到,数据库服务器与网络服务器是分开的。有两个数据库服务器,但一次只能运行一个。有一个中央存储,他们都从中读取,所以只有一个数据库实例。当一个数据库服务器出现故障时,另一个会联机。

话虽如此,将Windows服务部署到两个数据库服务器是否更有意义?这样可以确保一次只跑一次。

此外,你对运行Quartz有什么想法。NET?正如millimouse所提到的,我不一定需要它在web前端运行,然而,这样做可以让我不将windows服务部署到多台机器上,我认为无论哪种方式都不会有性能差异。想法?

感谢大家迄今为止的投入。如果需要任何其他信息,请告诉我。

当站点位于多台服务器上时,每分钟运行一次自动化任务的最佳方式

我必须解决您现在面临的确切问题。

首先,你必须意识到,你绝对不能在ASP.NET中可靠地运行长时间运行的进程。如果你从global.asax实例化调度程序类,你就无法控制该类的生存期。

换句话说,IIS可能会决定在任何时候回收承载类的工作进程。充其量,这意味着你的类将被销毁(你对此无能为力)。在最坏的情况下,你们班会在做作业的过程中被杀死。哎呀。

运行长寿命进程的适当方法是在计算机上安装Windows服务。我会在每个网络盒子上安装服务,而不是在数据库上。

服务实例化Quartz调度程序。这样,您就知道,只要机器启动,您的调度程序就可以保证继续运行。当作业运行时,Quartz只需在您指定的IJob类上调用一个方法。

class EmailSender : Quartz.IJob
{
    public void Execute(JobExecutionContext context)
    {
        // send your emails here
    }
}

请记住,Quartz在单独的线程上调用Execute方法,因此必须小心线程安全。

当然,您现在可以在多台机器上运行相同的服务。虽然听起来你很担心这一点,但你实际上可以把它变成一件积极的事情!

我所做的是在数据库中添加一个"锁定"列。当发送作业执行时,它会通过设置锁定列来锁定队列中的特定电子邮件。例如,当作业执行时,生成一个guid,然后:

UPDATE EmailQueue SET Lock=someGuid WHERE Lock IS NULL LIMIT 1;
SELECT * FROM EmailQueue WHERE Lock=someGuid;

通过这种方式,您可以让数据库服务器处理并发性。UPDATE查询告诉DB将队列中的一封电子邮件(当前未分配)分配给当前实例。然后,您SELECT锁定的电子邮件并发送它。发送后,从队列中删除电子邮件(或以您处理已发送电子邮件的方式),并重复此过程,直到队列为空。

现在您可以向两个方向扩展:

  • 通过在多个线程上同时运行同一作业
  • 由于它在多台机器上运行,您可以在所有服务器上有效地负载平衡发送工作

由于锁定机制,您可以保证队列中的每个电子邮件只发送一次,即使多台机器上的多个线程都在运行相同的代码。


针对评论:在我最终得到的实现中有一些差异。

首先,我的ASP应用程序可以通知服务队列中有新的电子邮件。这意味着我甚至不必按时间表运行,我可以简单地告诉服务何时开始工作。然而,这种通知机制在分布式环境中很难正确使用,因此只需每隔一分钟左右检查一次队列就可以了。

你的间隔时间实际上取决于你发送电子邮件的时间敏感性。如果需要尽快发送电子邮件,您可能需要每30秒甚至更短时间触发一次。如果不是那么紧急,你可以每5分钟检查一次。Quartz限制了一次执行的作业数量(可配置),并且您可以配置如果错过触发器会发生什么,因此您不必担心备份数百个作业。

其次,我实际上一次锁定5封电子邮件,以减少DB服务器上的查询负载。我处理的是高容量,所以这有助于提高效率(减少服务和DB之间的网络往返)。这里需要注意的是,如果一个节点在发送一组电子邮件的过程中发生故障(无论出于什么原因,从异常到机器本身崩溃),会发生什么。您将在DB中得到"锁定"的行,而没有任何服务。群体规模越大,这种风险就越大。此外,如果所有剩余的电子邮件都被锁定,空闲节点显然无法处理任何事情。

就线程安全而言,我指的是一般意义上的线程安全。Quartz维护一个线程池,所以您不必担心实际管理线程本身。

您必须小心工作中的代码访问的内容。根据经验,局部变量应该是好的。但是,如果您访问函数范围之外的任何内容,线程安全是一个真正的问题。例如:

class EmailSender : IJob {
    static int counter = 0;
    public void Execute(JobExecutionContext context) {
        counter++; // BAD!
    }
}

此代码不是线程安全的,因为多个线程可能同时尝试访问counter

Thread A           Thread B
Execute()
                   Execute()
Get counter (0)
                   Get counter (0)
Increment (1)
                   Increment (1)
Store value
                   Store value
            counter = 1 

counter应该为2,但我们有一个非常难以调试的竞争条件。下次运行此代码时,可能会发生以下情况:

Thread A           Thread B
Execute()
                   Execute()
Get counter (0)
Increment (1)
Store value
                   Get counter (1)
                   Increment (2)
                   Store value
            counter = 2

你会弄不明白为什么这次能奏效。

在您的特定情况下,只要您在每次调用Execute时创建一个新的数据库连接,并且不访问任何全局数据结构,您就可以了。

您必须更加具体地了解您的体系结构。电子邮件队列在哪里;在内存还是数据库中?如果它们存在于数据库中,则可以有一个名为"处理"的标志列,当任务从队列中获取电子邮件时,它只获取当前未处理的电子邮件,并将其获取的电子邮件的处理标志设置为true。然后将并发问题留给数据库。