在 asp.net-mvc 中,在不影响其他用户的情况下进行昂贵操作的正确方法是啥?

Posted

技术标签:

【中文标题】在 asp.net-mvc 中,在不影响其他用户的情况下进行昂贵操作的正确方法是啥?【英文标题】:In asp.net-mvc, what is the correct way to do expensive operations without impacting other users?在 asp.net-mvc 中,在不影响其他用户的情况下进行昂贵操作的正确方法是什么? 【发布时间】:2017-04-23 13:47:45 【问题描述】:

I asked this question 大约 5 年前,围绕如何“卸载”用户无需等待的昂贵操作(例如审计等),以便他们更快地在前端获得响应。

我现在有一个相关但不同的问题。在我的 asp.net-mvc 上,我构建了一些报告页面,您可以在其中生成 excel 报告(i am using EPPlus)和 powerpoint 报告(i am using aspose.slides)。这是一个示例控制器操作:

    public ActionResult GenerateExcelReport(FilterParams args)
    
        byte[] results = GenerateLargeExcelReportThatTake30Seconds(args);
        return File(results, @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", "MyReport.xlsx");
    

该功能运行良好,但我想弄清楚这些昂贵的操作(某些报告可能需要长达 30 秒才能返回)是否影响了其他用户。在上一个问题中,我有一个昂贵的操作,用户 DIDN"T 必须等待,但在这种情况下,他必须等待它作为同步活动(单击生成报告,期望用户在完成后得到报告)

在这种情况下,我不在乎主用户必须等待 30 秒,但 我只是想确保我不会因为生成文件的昂贵操作而对其他用户产生负面影响

对于这个用例,asp.net-mvc 中是否有任何最佳实践?

【问题讨论】:

异步方法真的很好,试试这篇文章asp.net/mvc/overview/performance/… 【参考方案1】:

您可以尝试将 Hangfire 和 SignalR 结合使用。使用 Hangfire 启动后台作业并放弃 http 请求。报告生成完成后,使用 SignalR 生成推送通知。

SignalR notification from server to client

替代选项是在客户端实现轮询机制。 发送 ajax 调用以对 hangfire 作业进行排队以生成报告。 然后使用另一个提供状态的 ajax 调用开始轮询一些 api,一旦报告准备好,就检索它。我更喜欢使用 SignalR 而不是轮询。

如果报表处理正在影响 Web 服务器上的性能,则将该处理卸载到另一台服务器。您可以使用消息传递(ActiveMQ 或 RabbitMQ 或您选择的其他框架)或 rest api 调用在另一台服务器上启动报告生成,然后再次使用消息传递或 rest api 调用将报告生成完成通知回 Web 服务器,最后是 SignalR通知客户。这将使 Web 服务器响应更快。

更新 关于你的问题

asp.net-mvc 中是否有针对此用例的最佳实践

您必须监控您的应用程序超时。监控客户端和服务器端。您可以依赖的工具很少,例如 newrelic、应用程序动态。我使用过 newrelic,它具有跟踪客户端浏览器和服务器端问题的功能。产品名称为“NewRelic Browser”和“NewRelic Server”。我相信还有其他工具可以捕获类似的信息。

分析指标超时,如果您发现任何异常,然后采取适当的措施。如果您观察到服务器端 CPU 和内存峰值,请尝试在同一时间范围内捕获客户端的指标。在客户端,如果您发现任何超时问题,连接错误意味着您的应用程序用户在服务器执行繁重工作时无法连接到您的应用程序。接下来尝试识别服务器端瓶颈。如果没有足够的空间来调整代码的性能,那么通过一些服务器容量规划练习并找出如何进一步扩展您的硬件或将后台作业移出 Web 服务器以减少负载。仅使用这些工具捕获指标可能还不够,您可能需要检测(日志捕获)您的应用程序以捕获其他指标以正确监控应用程序的运行状况。

Here你可以从微软那里找到一些关于.net应用程序容量规划的信息。

-维诺德。

【讨论】:

【参考方案2】:

这些都是关于如何将工作移出请求/响应周期的好主意。但我认为@leora 只是想知道长时间运行的请求是否会对 asp.net 应用程序的其他用户产生不利影响。

答案是否定的。 asp.net 是多线程的。每个请求都由一个单独的工作线程处理。

【讨论】:

【参考方案3】:

一般来说,在后台运行长时间运行的任务并在工作完成时向用户发出某种通知是一种很好的做法。您可能知道 Web 请求执行时间限制为 90 秒,因此如果您的长时间运行任务可能超过此时间,您别无选择,只能在其他线程/进程中运行。如果您使用.net 4.5.2,您可以使用HostingEnvironment.QueueBackgroundWorkItem 在后台运行长时间运行的任务,并使用SignalR 在任务完成执行时通知用户。如果您正在生成一个文件,您可以将其存储在具有一些唯一 ID 的服务器上,并向用户发送一个下载链接。您可以稍后删除此文件(例如使用某些 Windows 服务)。

正如其他人所提到的,有一些更高级的后台任务运行器,例如 Hangfire、Quartz.Net 等,但总体概念是相同的 - 在后台运行任务并在完成时通知用户。这里有一些不错的article,关于运行后台任务的不同选项。

【讨论】:

【参考方案4】:

您需要使用async and await of C#。

根据您的问题,我认为您只关心请求可能占用比应有的更多资源这一事实,而不是可扩展性。如果是这种情况,请使您的控制器操作以及您调用的所有操作异步,只要它们涉及阻塞线程的调用。例如如果您的请求通过电线或 I/O 操作,它们将在没有异步的情况下阻塞线程(从技术上讲,您会这样做,因为您将在继续之前等待响应)。通过异步,这些线程变得可用(在等待响应时),因此它们可以潜在地为其他用户的其他请求提供服务。

我假设您并没有犹豫如何扩展请求。如果你是,请告诉我,我也可以提供这方面的详细信息(除非需要,否则无法编写)。

【讨论】:

【参考方案5】:

我相信像Hangfire 这样的工具/库是您正在寻找的。首先,它允许您指定在后台线程(在同一应用程序/进程中)运行的任务。使用各种技术,例如SignalR 允许实时前端通知。

但是,我在使用 Hangfire 近一年后设置的东西是使用 this documentation. 将我们的作业处理(和实现)拆分到另一台服务器上,我使用内部 ASP.NET MVC 应用程序来处理另一台服务器上的作业。那么,唯一的性能瓶颈是两个服务器是否使用相同的数据存储(例如数据库)。如果您锁定了数据库,那么解决它的唯一方法就是尽量减少对所述资源的锁定,无论您使用哪种方法。

我使用接口来触发作业,存储在一个通用库中:

public interface IMyJob

    MyJobResult Execute( MyJobSettings settings );

还有,在前端应用程序中找到的触发器:

//tell the job to run
var settings =  new MyJobSettings();
_backgroundJobClient.Enqueue<IMyJob>( c => c.Execute( settings ) );

然后,在我的后台服务器上,我编写实现(并将其挂接到我正在使用的Autofac IOC container):

public class MyJob : IMyJob

    protected override MyJobResult Running( MyJobSettings settings )
    
         //do stuff here
    

我并没有过多地尝试让 SignalR 在两个服务器上工作,因为我还没有遇到那个特定的用例,但我想这在理论上是可能的。

【讨论】:

【参考方案6】:

您需要监控您的应用程序用户以了解其他用户是否受到影响,例如通过记录响应时间

如果您发现这会影响其他用户,您需要在另一个进程中运行该任务,可能在另一台机器上。您可以使用库Hangfire 来实现这一点。

【讨论】:

谢谢。我看过 Hangfire,当我运行主要用户不关心的昂贵操作(例如审核或向其他用户发送带外电子邮件通知)时,这似乎是一个很好的解决方案,但在这种情况下,主要用户是等待特定的响应,所以我看不出在这种情况下hangfire 是如何工作的。 正如 Vinod 所说,当 hangfire 作业完成时,使用一些通知系统或轮询来提醒用户。【参考方案7】:

使用该答案,您可以声明一个低优先级的任务

lowering priority of Task.Factory.StartNew thread

 public ActionResult GenerateExcelReport(FilterParams args)
 
     byte[] result = null;
     Task.Factory.StartNew(() =>
     
        result =  GenerateLargeExcelReportThatTake30Seconds(args);
     , null, TaskCreationOptions.None, PriorityScheduler.BelowNormal)
      .Wait();

     return File(result, @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", "MyReport.xlsx");
 

【讨论】:

这并不能真正解决问题。【参考方案8】:

将作业排入表中,并让后台进程轮询该表以确定接下来需要运行哪个超大作业。然后,您的 Web 客户端将需要轮询服务器以确定作业何时完成(可能通过检查数据库中的标志,但还有其他方法。)这可以保证您不会拥有超过一个(或者无论您拥有多少决定是否合适)一次运行这些昂贵的进程。

Hangfire 和 SignalR 可以在这方面为您提供帮助,但是当五个用户同时请求同一进程时,排队机制确实是必要的,以避免出现重大中断。提到的启动新线程或后台进程的方法似乎没有提供任何机制来最小化处理器/内存消耗以避免因消耗过多资源而干扰其他用户。

【讨论】:

以上是关于在 asp.net-mvc 中,在不影响其他用户的情况下进行昂贵操作的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 asp.net-mvc 控制器中集中授权逻辑?

ASP.NET-MVC中Entity和Model之间的关系

asp.net-mvc:js文件中的剃刀'@'符号

剑道 DropDownListFor() 与 ASP.NET-MVC

在 asp.net-mvc 中,查询字符串太长会导致 404 File not found 错误吗?

在 asp.net-mvc 中从服务器读取文本文件的最佳方法是啥