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

In asp.net-mvc, what is the correct way to do expensive operations without impacting other users?

I asked this question 大约 5 年前围绕如何 "offload" 用户不需要等待的昂贵操作(例如 auditng 等)以便他们在前端获得响应更快。

我现在有一个相关但不同的问题。在我的 asp.net-mvc 上,我构建了一些报告页面,您可以在其中生成 excel 报告 (i am using EPPlus) and powerpoint reports (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 秒才能到达 return)是否正在影响 其他 用户.在上一个问题中,我有一个用户不必等待的昂贵操作,但在这种情况下,他确实必须等待,因为它是一个同步 activity(单击“生成报告”,期望用户得到一个完成后报告)

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

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

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

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

您可以尝试结合使用 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 服务器响应更快。

更新 关于你的问题

Is there any best practice here in asp.net-mvc for this use case

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

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

Here您可以从 Microsoft 找到一些有关 .net 应用程序容量规划的信息。

-维诺德.

一般来说,在后台 运行 长 运行ning 任务并在任务完成时向用户发出某种通知是一种很好的做法。正如您可能知道的 Web 请求执行时间限制为 90 秒,因此如果您的长 运行ning 任务可能超过此时间,您别无选择,只能 运行 其他 thread/process。如果您使用的是 .net 4.5.2,您可以使用 HostingEnvironment.QueueBackgroundWorkItem for running long running tasks in background and use SignalR 在任务执行完成时通知用户。如果您正在生成一个文件,您可以将其存储在具有一些唯一 ID 的服务器上,并发送给用户一个 link 以供下载。您可以稍后删除此文件(例如使用某些 windows 服务)。

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

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

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");
 }

我相信 tool/library 例如 Hangfire is what your looking for. First, it'll allows for you to specify a task run on a background thread (in the same application/process). Using various techniques, such as 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 在两个服务器上工作,因为我还没有 运行 进入那个特定的用例,但我想这在理论上是可能的。

您需要使用async and await of C#

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

我假设您不会徘徊于如何扩展请求。如果您是,请告诉我,我也可以提供详细信息(除非需要,否则要写的太多了)。

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

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

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

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