it-swarm.cn

在大型站点上服务后台任务

我们正在处理StackOverflow上一个有趣的问题。

我们有一堆小的“需要尽快完成的任务”。一个示例是更新“相关问题”列表。过去我们所做的是将这些任务附加到某些用户的页面加载中。

这从来都不是理想的,但并不是很明显。现在SO已经通过了1,000,000个问号,那些倒霉的用户开始感受到它了。

自然的解决方案是将这些任务实际推入后台。我正在考虑有两种广泛的方法可以做到这一点。

1.在IIS中,作为自定义线程池/工作队列

基本上,我们启动了几个(非- ThreadPool ,以便不干扰IIS)线程,并让它们为我们将 Funcs 推入的某些集合提供服务。

这里的最大优点是简单性。我们不必担心会封送任何东西,也不必确保某些外部服务正常运行并做出响应。

我们还可以访问所有通用代码。

缺点是,我们不应该使用后台线程。我知道的反对意见都集中在饥饿IIS(如果您使用ThreadPool))和线程随机死亡(由于AppPool回收)的集中。

我们已经有了现有的基础架构,以使随机线程死亡成为非问题(基本上可以放弃检测任务的可能性),并且限制线程数量(并使用非ThreadPool线程)也不难。

我是否还缺少对IIS进程线程池/工作队列的其他反对意见?

已移至StackOverflow ,因为此处并未真正解决。

2.作为服务

某些第三方解决方案或定制解决方案。

基本上,我们会将任务跨流程边界编组到某个服务,而不必理会它。大概我们是在某些代码中链接或限制为原始SQL +连接字符串。

优点是这样做的“正确方法”。

缺点是我们要么只能做有限的工作,要么必须制定一些系统来使该服务与我们的代码库保持同步。我们还需要以某种方式挂钩所有监视和错误日志记录,这些都是通过“ In IIS”选项免费获得的。

服务方法是否还有其他好处或问题?

简而言之,是否存在无法预见和无法克服的问题,从而使方法1变得不可行,如果是的话,我们是否应该寻求方法2的良好第三方服务?

49
Kevin Montrose

几周前,我问了一个 类似的问题 。简而言之,我一段时间以来的方法一直是开发Windows服务。我将使用NServiceBus(本质上是MSMQ)将请求从我的Web应用程序封送给我的服务。我曾经使用WCF,但是要使分布式事务在WCF上正常工作总是让人感到痛苦。 NServiceBus可以解决问题,我可以在事务中提交数据并创建任务,而不必担心我的服务当时是否已启动并正在运行。举一个简单的例子,如果我需要发送电子邮件(例如注册电子邮件),我将创建用户帐户并在事务中向Windows服务发出信号(发送电子邮件)。服务端的消息处理程序将提取消息并进行相应处理。

由于已经发布了ASP .NET 4.0和AppFabric,因此,对于上述机制有许多可行的替代方案。回到上面提到的问题,我们现在有了AppFabric的AppInitialize(通过网络。管道)以及ASP .NET 4.0的自动启动功能,这使得将Windows Services开发为Web应用程序成为一种可行的选择。出于多种原因,我现在开始这样做(最大的原因是部署不再是一件麻烦事):

  1. 您可以在服务上开发Web UI(因为它作为Web应用程序运行)。这对于查看运行时正在发生的事情非常有用。
  2. 您的Web应用程序的部署模型将适用于您的服务应用程序。
  3. IIS提供了一些精巧的功能来处理应用程序故障(在某些方面类似于Windows服务)。
  4. Web开发人员(自然)对开发Web应用程序非常熟悉,大多数人在开发Windows Service时对最佳实践并不了解。
  5. 它提供了许多替代方法来公开API供其他应用使用。

如果您走这条路(请原谅我从原始帖子中进行复制和粘贴),我肯定会考虑在单独的Web应用程序中运行后台逻辑。原因有很多:

  1. 安全性 UI可能有不同的安全模型,用于显示有关正在运行的后台进程的信息。除了操作团队之外,我不想将此UI公开给其他任何人。而且,Web应用程序可以以具有提升权限集的其他用户身份运行。
  2. 维护。能够将更改部署到托管后台进程的应用程序而不会影响用户对前端网站的使用,将是很棒的。
  3. 性能将应用程序与处理用户请求的主站点分开意味着后台线程将不会削弱IIS处理传入请求队列的能力。此外,如果需要,处理后台任务的应用程序可以部署到单独的服务器上。

这样做可以回到编组方面。 WCF,NServiceBus/RabbitMQ/ActiveMQ等,Vanilla MSMQ,RESTful API(认为MVC)都是选项。如果您使用的是Windows Workflow 4.0,则可以公开您的Web应用程序可以使用的主机终结点。

对我来说,用于服务的Web托管方法还算陌生,只有时间能证明这是正确的选择。到目前为止到目前为止还不错。顺便说一句,如果您不想使用AppFabric(由于某种奇怪的原因,我不能,不支持Windows Server Web Edition),那么Gu的帖子中提到的自动启动功能就可以很好地工作。但是,远离applicationhost.config文件,该帖子中的所有内容都可以通过IIS)控制台(主服务器级别上的配置编辑器)进行设置。

注意:我最初在此消息中发布了更多链接,但是,这是我对此交流的第一篇帖子,仅支持一个链接!基本上有另外两个,让他们获得Google“ Windows服务之死...万岁AppFabric!”和“ auto-start-asp-net-applications”。对于那个很抱歉。

17
Rohland

Windows中实际上有第三种方法来运行后台服务,这在UNIX世界中非常普遍。第三种方式是CRON作业,该作业运行您的基础架构的一部分。在Windows中,这称为task scheduler,这对于按计划运行代码非常普遍。要使用此功能,您将创建一个按预定时间表执行的命令行应用程序。这样做的好处是您不必担心进程是否像服务一样正常启动和运行,因为如果由于某种原因它失败了,它将在下次启动。

至于封送特定任务,您实际上只需要将这些任务存储在持久性二进制存储中。直到命令行应用程序从存储中选择它们并执行它们。我过去使用Cassandra数据库作为会话状态提供程序来完成此任务,以便为Cassandra=)数据库中的特定用户填充后台任务,然后使用命令行选择它们并为用户执行它们。

这可能不是典型的封送处理解决方案,但是它对我来说效果很好,并且事实证明这是一个非常优雅的解决方案,因为计划任务在关机,网络问题后仍然存在,并且任何机器都可以执行任务,因为它集中在中央存储。

无耻的晋升,但这是我的项目,而我刚刚简要介绍的解决方案就是创建该项目的原因: http://github.com/managedfusion/fluentcassandra/

22
Nick Berardi

Cron +网络应用

这是一个经过实战检验的设计,它可以与您的Web场一起水平缩放并确保您使用的是web技术堆栈

运作方式如下:

  1. 在Web应用程序中创建控制器/操作以处理计划的后台任务。按照惯例,我通常称我的http://mydomain.com/system/cron
  2. 为了安全起见,此操作应仅锁定到本地网络上经过身份验证的IP地址。
  3. 在单独的计算机上,安装 Wget 并设置 Scheduled Task ,以使wget从步骤1中获取资源。您可以根据需要频繁地运行任务(通常选择30秒)。不要忘记将适当的cookie参数传递给Wget,以便对您的Web应用程序进行身份验证。
  4. 为了实现冗余,您还可以在第二台计算机上安装第二个计划的wget。

万岁!现在您有了一条将每30秒调用一次的路由。而且,如果处理该请求需要5分钟,那么没有人会在意,因为它不是用户页面请求的一部分。

cron动作看起来非常简单:他具有按一定频率执行的方法列表。当请求进入时,他会查看是否需要执行一个方法,并调用适当的方法。这意味着您可以控制数据库中的计划,在该数据库中您可能已经有许多其他重要的配置数据。

更重要的是(对您而言),这意味着您不必按固定的时间表调用作业。您可以编写想要确定何时执行方法的任何逻辑。

利弊

  • 您已经非常擅长编写ASP.NET MVC代码,因此这使您可以在同一平台中编写后台任务,并在其中编写其余的解决方案。
  • 这些任务在与您的Web应用程序相同的上下文中运行,因此您可以共享缓存并利用helper方法已经存在。
  • 如果您有wget获取load-balancedURI,那么您的后台任务现在也已实现了负载均衡。
  • 同时部署-您不必担心将Web应用程序与后台任务逻辑同步,因为它们都在同一部署中。
  • 多年以来,一些人告诉我这种设计是“高度耦合的”,但是当他们被压迫时,他们仍然无法阐明为什么这是一件坏事。

注意:如果有任何疑问或疑虑,请请添加评论。我很高兴阐述。

10
Portman

我已经尝试并在当前应用程序中使用了几乎所有可能的方法。我开始做与您当前相同的操作,背对用户请求填充数据,然后将其缓存。我意识到这也不是一个好主意(尤其是当您扩展到多个Web服务器时,更多的用户会受到打击)。

我也有一个计划中的工作,它会在ASP.NET应用程序中命中URL-这是一个不错的解决方案,但是当您扩展到超过1台Web服务器时,它开始崩溃。

目前,我使用两种不同的方法,都使用Quartz.NET,这是一个很棒的小库。首先是Quartz.NET与ASP.NET一起在进程内运行,它是在global.asax中设置的,每隔几分钟运行一次。我用它来带外更新ASP.NET缓存,这是它作为ASP.NET的一部分运行的唯一原因。

第二个是我写了一个包装Quartz.NET的库DaemonMaster-它很容易将DLL)放到目录中并在Windows服务中运行。我发现它有助于避免使用Windows服务的一些烦人的部分,还需要清理Quartz.NET api。通过DaemonMaster运行的服务具有两种不同的风格,第一种是需要每晚或每隔X分钟运行的作业。其他作业根据来自ASP.NET应用程序的数据进入队列,ASP.NET应用程序将Rabbit对象放在RabbitMQ上,服务轮询RabbitMQ然后处理数据。

基于此,我建议您使用Windows服务(并检出DaemonMaster),并在需要时使用RabbitMQ之类的队列将数据从ASP.NET应用程序传递到服务-在所有这些解决方案中,它都表现最好。如果您正在加载缓存,则在ASP.NET中运行是有意义的,否则我认为不会。

7
James Avery

我会以正确的方式进行操作,并运行Windows服务来监视“队列”。我之所以说“队列”,是因为使用MSMQ进行编程类似于将热门扑克插入您的眼球。

我已经爱上了Rails中 Delayed :: Job 的简单性,并且在.NET中可以轻松完成类似的操作。

基本上,您添加了任何种类的SomethingOperation(具有Perform()方法的东西)。然后,只需序列化相关参数,为其赋予优先级,某种默认重试行为并将其填充到数据库中即可。

您的服务将仅对此进行监视并处理队列中的作业。

6
Ben Scheirman

我们对服务总线/消息队列/服务方法非常满意。基本架构是这样的。

网站将消息发送到队列

bus.Send(new ProjectApproved()); // returns immediately

Windows服务在自己的时间内接收和处理消息

public class DoesSomethingAwesome : ConsumerOf<ProjectApproved>
{
   public void Consume(ProjectApproved Message)
   {
      // Do something "offline"
   }
}

优点是用户连接的前端服务也没有延迟。 Windows服务可以关闭并升级,而不会中断主站点。加上极快

如果您不能在消息中存储所有数据,则可以随时存储并在以后检索。我建议使用一种文档存储机制,例如: RavenDBMongoDB ,在这种情况下,无需更改即可直接存储您的类。

网站将消息发送到队列

// Save your object
store.Save(completeProject);

// Send a message indicating its ready to be processed
bus.Send(new ProjectApproved() { ProjectId = completeProject.Id });

Windows服务在自己的时间内接收和处理消息

public class DoesSomethingAwesome : ConsumerOf<ProjectApproved>
{
   public void Consume(ProjectApproved Message)
   {
      // Retrieve your object back
      var completeProject = store.Get(Message.ProjectId);
   }
}

为了使事情简单,我们使用: Rhino ESBTopshelf 。配置非常简单,并且将其用于现有应用程序已证明只需很少的时间。

4
Nathan Palmer

我很好奇为什么两者都不是可行的选择。现在,您触发了页面视图上的作业,其中一些不幸的树液被卡住,等待10秒钟才能显示页面。至少那是我对您当前方法的理解。

但是,随着网站的增长,这些工作的运行时间越来越长,并且您不想破坏网站上的用户体验。一天当中,甚至没有几个(或很多)不幸的用户,所以现在您正在考虑在后台调度作业。

我不明白为什么定期运行的后台作业无法模仿访客。现在我不是Windows程序员,但是在Linux世界中,我将设置一个cron作业,该作业定期运行,并且有两行代码。

#!/bin/bash
wget -O /dev/null http://stackoverflow.com/specially_crafted_url

它结合了两个系统的优点。它是在后台完成的。它不会影响用户。它仍然使用页面视图开始工作。我以前见过这种方法。它往往是古老的简单方式与未来复杂方式之间的中间地带。

更新

我认为您可以通过在Web服务器本身上运行作业运行程序来解决负载平衡问题。作业运行者将URL从作业队列中拉出,并按如下方式运行它:

wget -O /dev/null http://localhost/specially_crafted_url

由于作业/消息队列的性质,作业将在作业运行者之间平均分配,这意味着special_crafted_url最终将在您的Web服务器之间分配。

3
mellowsoon

我认为纯服务方法的缺点是,您会将代码分散到服务中,并且远离核心应用程序。

这是我们对大型后台非时间敏感型作业所做的工作,这些作业将代码保持在一起并简化了服务:

  1. 创建一个作业队列(内存中或数据库中,无论作业类型需要什么持久性)
  2. 创建一个将执行排队作业的Web服务
  3. 死了的简单服务应用程序,它以指定的时间间隔调用Web服务,而将所有复杂的内容(作业检索和执行)留给核心代码库中的Web服务。

甚至更简单,只需在控制台应用程序中进行调用,然后使用Task Scheduler或VisualCron将其转换为“服务”即可。

2
Brandon

Resque 很好。甚至 (Kthxbye )如果需要在完成后将结果值通知您。

都基于Redis/Ruby。

老实说,如果您正在执行基于服务的方法,那么实际上并不需要将其与您当前的平台进行超级集成,我认为这是一个加分。我希望它可以是一个设置后遗忘的系统,可以运行(带有某种监视)并完成工作。我不知道它是否必须在同一平台上运行,因为它只是更新/修改数据库信息。

可以肯定的是,如果将这种工作移植到一个单独的实体中,那么您可以花更少的钱得到更多的收益,尤其是因为看来您正在处理线程问题。 ResqueKthxbye 都将处理移到单独的进程中,以允许OS处理并发性。

重覆

Kthxbye

1
Lukas

我喜欢TopShelf。保持简单性,但仍以Windows服务运行的正确方式进行操作。基本上创建一个控制台应用程序,添加大约15-20行代码,然后将其作为服务安装。

http://code.google.com/p/topshelf/

1
Shane

如何在网络服务器上运行一个非常简单的Windows服务,并定期命中一个执行其他任务的维护URL,该怎么办?让它限制在任何给定请求中执行多少工作。

1
Rob Sobers

我将在这里推翻明显的趋势,并建议使用IIS中的模型。我自己用过,效果很好。实现一个体面的线程池类真的不是那么难(多年来,我扩展了我的线程池类,以支持动态创建和销毁线程,重试作业等)。优点是:

  • 无需外部服务监控
  • 实施简单:无需跨流程编组,无需高级作业监控
  • 您仍然在IIS)进程中,因此您可以执行所有常规日志记录,依此类推(无需多个日志文件)
  • 大大简化了部署(更新服务时,您必须停止服务,复制文件,启动服务-这是对网站代码的常规更新之外的附加功能)

我认为,IIS内部解决方案只是将工作附加到随机页面视图上的“下一步”。

1
Dean Harding

任务队列Java API概述

任务概念
在App Engine后台处理中,任务是对一小部分工作的完整描述。此描述包括两个部分:

  • 参数化任务的数据有效负载。
  • 实现任务的代码。

任务作为脱机Web挂钩
幸运的是,Internet已经以HTTP请求及其响应的形式提供了这样的解决方案。数据有效载荷是HTTP请求的内容,例如Web表单变量,XML,JSON或编码的二进制数据。代码参考是URL本身。实际的代码是服务器在准备响应时执行的逻辑。

0
antony.trupe

我将使用WAS托管的WCF服务来侦听MSMQ队列。

专业的

  • 激发并忘记来自Web应用的消息

  • MSMQ/WCF限制并重试

  • 保证交货; D

  • 死信管理

  • 分布式处理

  • WAS/MSMQ激活

骗子

  • MSMQ(还没有死……)

WCF中的MSMQ功能使使用MSMQ确实不错。是的,您将流血于配置,但好处将超过牺牲。

0
Adam

开发Web应用程序时,我遇到过几次。我们一直在通过创建一个执行任务的Windows控制台应用程序,以及创建一个经常运行以实际执行任务的计划任务来解决该问题。

0
John Christensen

您可以使用Rx和类似以下内容将工作分流到一个或多个后台线程上:

var scheduler = new EventLoopScheduler( SchedulerThreadName );
_workToDo = new Subject<Action>();
var queueSubscription = _workToDo.ObserveOn( scheduler ).Subscribe( work => work() );
_cleanup = new CompositeDisposable( queueSubscription, scheduler );

使用方法:

var work = () => { ... };
_workToDo.OnNext( work ); // Can also put on error / on complete in here

将所有这些都托管在一个班级中,只有一个班级(又名单身人士,但请正确执行-使用IoC容器确定生活方式)。

您可以通过编写自定义调度程序来代替使用EventLoopScheduler(运行单个线程)来控制线程池的大小。

0
Neal

两者都做

在问题路径中添加一个可选参数,以完成您当前根据用户请求进行的工作:

在大型站点上为后台任务提供服务

创建一个在每台服务器上运行的控制台应用程序,并打开IIS日志共享二进制文件,并将其读取到文件的当前末尾。使用filesystemwatcher或定时间隔向前读取以收集更新为IIS刷新了日志。

使用此信息来确定当前查看过哪些页面。

使用已解析日志中的页面URL来通过Webclient对象在localhost上调用URL的“额外版本”。

添加一些代码以在每个日志周期结束时切换文件,或者在每个日志周期重新启动该过程。

0
Bill

我已经实施过几次这种事情。在Windows上,我设置了一个python命令行程序,该程序在不同时间执行某些操作。该程序还公开了端口上的xmlrpc接口。然后,计划任务作业每分钟运行一次,查询xmlrpc接口,如果没有启动,它会尝试启动它们,否则,会通过电子邮件发送给我。

好处是运行的作业不受cron或计划的约束。我有一个流程作业,它每秒钟运行一次,但是在开始新作业之间会等待的时间越来越长,具体取决于它是否有工作要做。而且,它可以用于根据结果进行智能操作。遇到500错误?真的有很长的延迟吗?做其他事情。通知其他服务。等等。

相同的系统可以在UNIX上运行,但需要进行少量修改。

0
Christopher Mahan

我自己没有答案,但是问题出了声-我记得有些随机的人 在播客上讨论过一次

Spolsky:我注意到您在博客上提出的一个问题是,您应该如何一般地处理维护重复性任务?

阿特伍德:是的。

Spolsky:这是一个公平的描述吗?每个网站都有一些您不想在加载网页时执行的任务,但是您希望以某种重复的方式执行。

阿特伍德:是的,后台任务有点类似。

Spolsky:是的,那么您发现了什么?

阿特伍德(Atwood):嗯,我最初在Twitter上问,因为我只想要一些重量轻的东西。我真的不想写Windows服务。我觉得那是带外代码。加上实际上完成工作的代码实际上是一个网页,因为对我来说,网站上的逻辑工作单元是一个网页。因此,确实就像我们正在回拨该网站一样,就像该网站中的另一个请求一样,因此我将其视为应该保持内联的东西,而在Twitter上向我推荐的这种小方法本质上是要以固定的到期时间将某些内容添加到应用程序缓存中,然后您要进行回调,以便在到期时调用某个起作用的函数,然后以相同的到期时间将其添加回缓存中。所以,这有点,也许“贫民窟”是正确的词。

0
Oddthinking