20

我们有一个网站应用程序,我们希望在全年的多个时间点都能获得令人难以置信的高流量。我们目前有一些第三方负载平衡软件,可以在繁忙期间将用户重定向到“等待”页面,以防止我们的 Web 应用程序服务器被传入的请求量窒息。

展望未来,我们希望对这个过程有更多的控制,并实现某种虚拟队列。当前的负载均衡器没有排队功能,只是根据速率限制允许流量通过。这是随机的,并且在您刷新页面(或自动刷新)时很幸运。

我在网上做了一些关于这个的阅读,但发现很少有关于如何实现一个非常基本的虚拟 HTTP 请求队列的实现细节。当然,也有一些公司将其作为成熟的服务提供,例如queue-itNetprecept,但这些对于我们当前的需求来说似乎有些过分(而且非常昂贵)。

有问题的 Web 应用程序是用 ASP.Net MVC 编写的。请记住,目前我们不需要“队列优先级”等高级功能,我使用静态队列管理器类创建了一个非常基本的概念验证,使用ConcurrentQueue<T>等,但我想知道这是否是一个有效的、可扩展的方法?这可以成为主要应用层的一部分吗?还是应该分开保存?有没有人知道如何将这种功能实现到 ASP.Net MVC 应用程序中?


编辑:感谢到目前为止的答案。大多数答案似乎都涉及到很多关于缓存的细节。这已经(非常)在我们的网站上大量使用,使用 ASP.Net Web 缓存、在负载均衡器级别缓存整页请求以及使用 AppFabric 进行对象缓存。

能够管理队列的原因是因为该过程是非常数据库写入繁重的。我们正在通过网站有效地为某种产品创建订单。这意味着这些数据库事务正在考虑诸如最后一分钟的库存检查等事情。这就是出现性能问题的地方,这也是想要实现某种排队系统的原因。

在数据库服务器上投入更多资源不是一个现实的选择。我真的在寻找这种性质的排队系统(C# 或其他)的技术实现细节。抱歉,如果最初没有明确说明。

4

7 回答 7

26

在衡量应用程序的性能时,您是否考虑以下几点?

  1. 缓存
  2. 无会话控制器
  3. 异步控制器

输出缓存:

也许 MVC3( Performance-Wise )最有用的特性是输出缓存。当您的应用程序确实必须获取数据、对其进行计算并返回数据时,实际上会发生最大的性能损失。输出缓存可以缓存这些结果,因此它们可以直接返回,甚至无需接触数据库。尤其是在执行复杂查询时,这可以显着降低服务器上的负载(实际上,通过在 Web 应用程序中仔细实现缓存,您可以将服务器上的负载降低 90%)。

namespace MvcApplication1.Controllers
{
     public class DataController : Controller
     {
          [OutputCache(Duration=10)]
          public string Index()
          {
               return DateTime.Now.ToString("T");    
          }
     }
}

无会话控制器:

禁用会话状态的控制器为不需要会话状态的控制器提供了优化。无状态控制器适用于不需要会话概念的情况。

默认情况下,ASP.NET 管道不会同时处理属于同一会话的请求。它对它们进行序列化,即按照它们被接收的顺序对它们进行排队,以便它们被串行处理而不是并行处理。这意味着如果一个请求正在进行中并且来自同一会话的另一个请求到达,它将排队等待仅在第一个请求完成时才开始执行。

让我们看一个例子;向服务器发出 3 个异步 AJAX 请求的页面,启用了会话状态(另请注意,必须实际使用会话,因为 ASP.NET 足够聪明,即使您从不使用会话状态,也不会序列化请求,即使它已启用)。

jQuery

$(document).ready(function () {
    //Make 3 concurrent requests to /ajaxtest/test
    for (var i = 0; i < 3; i++) {       
        $.post("/ajaxtest/test/" + i,
            function (data) {                       
                //Do something with data...
            },
            "json");
    }
});

控制器 - 动作方法

public class AjaxTestController : Controller
{       
    [HttpPost]
    public JsonResult Test(int? id)
    {
        Thread.Sleep(500);
        return Json(/*Some object*/);
    }
}

在此处输入图像描述

可以在网络配置文件中看到序列化请求的效果;每个请求比前一个请求花费大约 500 毫秒。所以这意味着我们没有从异步进行这些 AJAX 调用中获得任何好处。让我们再次查看为我们的 AjaxTestController 禁用会话状态的配置文件(使用 [SessionState] 属性)。

[SessionState(SessionStateBehavior.Disabled)]
public class AjaxTestController : Controller
{       
    //...As above
}

在此处输入图像描述

好多了!您可以看到 3 个请求是如何并行处理的,总共需要 500 毫秒才能完成,而不是我们在第一个示例中看到的 1500 毫秒。

异步控制器:

首先,控制器开始一个或多个外部 I/O 调用(例如,SQL 数据库调用或 Web 服务调用)。无需等待它们完成,它就会将线程释放回 ASP.NET 工作线程池,以便它可以处理其他请求。

稍后,当所有外部 I/O 调用完成时,底层 ASP.NET 平台从池中获取另一个空闲工作线程,将其重新附加到您的原始 HTTP 上下文,并让它完成对原始请求的处理。

在此处输入图像描述

如何测量繁忙交通下的响应时间?

我从此链接复制了以下内容。因为有时链接会被破坏所以我在这里保留了一些重要的部分。请检查此链接以获取更多详细信息

要了解异步控制器如何响应不同级别的流量,以及这与简单的同步控制器相比有何不同,您可以创建一个包含两个控制器的示例 MVC。为了模拟长时间运行的外部,它们都执行了一个需要 2 秒才能完成的 SQL 查询(使用 SQL 命令 WAITFOR DELAY '00:00:02'),然后它们将相同的固定文本返回给浏览器。其中一个同步执行;另一个异步。

在另一个示例中,您可以检查一个简单的 C# 控制台应用程序,该应用程序模拟了访问给定 URL 的大量流量。它只是一遍又一遍地请求相同的 URL,计算最后几个响应时间的滚动平均值。首先它只在一个线程上执行此操作,但随后在 30 分钟内逐渐将并发线程数增加到 150 个。如果您想尝试在您自己的网站上运行此工具,您可以下载 C# 源代码。

结果说明了有关异步请求如何执行的一些要点。查看这张平均响应时间与并发请求数的关系图(响应时间越短越好):

在此处输入图像描述

要理解这一点,首先我需要告诉您,我已将 ASP.NET MVC 应用程序的工作线程池人为地设置为 50 个工作线程的最大限制。我的服务器实际上有一个默认的最大线程池大小为 200——一个更合理的限制——但如果我减少它,结果会更清楚。如您所见,只要有足够的工作线程可以运行,同步和异步请求的执行完全相同。他们为什么不应该呢?但是一旦线程池用完(> 50 个客户端),同步请求必须形成一个队列来提供服务。基本排队理论告诉我们,在队列中等待的平均时间由以下公式给出:

在此处输入图像描述

这正是我们在图中看到的。排队时间随队列长度线性增长。(为我沉迷于使用公式而道歉——有时我就是无法抑制我内心的数学家。如果它成为问题,我会接受治疗。)不过,异步请求不需要这么快就开始排队。他们不需要在等待时阻塞工作线程,因此线程池限制不是问题。那么为什么当有超过 100 个客户时他们开始排队呢?这是因为 ADO.NET 连接池默认限制为 100 个并发连接。

希望这对您有所帮助。

于 2013-07-20T17:36:19.710 回答
3

IIS 有自己的队列,您可以为每个应用程序池配置这些队列。这是包含有关性能的有用提示的链接。

我不建议混合应用程序代码和处理低技术性能优化的代码。将其分开,因为它会使您的代码保持可维护性。

于 2013-07-15T10:20:00.057 回答
2

根据您的应用程序排队可能根本无法解决您的问题。如果您建立一个队列,您的服务器将同时执行较少的工作,因为运行的并发线程较少。

什么时候排队?

显然我必须扭转这个问题:你为什么要排队?可能是因为您的服务器无法处理大量传入流量。如果我们谈论仅持续几秒钟的峰值,这没什么大不了的:建立一个队列并在几秒钟内处理它。

队列只是一个桶

正如您的问题所述,这个峰值持续时间超过几秒钟,“全年有几个点”。因此,我假设您的服务器在一段时间内经历了大量新请求的涌入,这段时间足够长,以至于处理队列只会导致客户端超时。[仅供参考:查看网络中的存储桶模型。这些有相同的问题要处理]

让我举一个简单的例子:

这是夏天正常的一天,你在街角提供冷柠檬水。您每分钟最多可以处理 5 个客户,午餐期间的高峰意味着每分钟大约有 7 个客户在排队等候您的柠檬水。一分钟后,您有 2 个客户在等待,两分钟后,您有 4 个客户在等待您处理他们的订单 [等等]。

由于高峰涌入只持续了 10 分钟,因此您排队的时间最长为 20 个客户。

在另一天,它非常热,您的涌入峰值不是通常的每分钟 7 个客户,您的涌入峰值高达每分钟 42 个客户。这意味着一分钟后,您已经有 37 个客户在等待您的柠檬水。很明显——除了它是地球上最后的柠檬水——你的客户不会为了一杯柠檬水排队一个小时,然后就会离开。

这与您在服务器上的峰值有关的问题是相同的:如果您不断遇到比您可以处理的更多的传入连接,您的队列将增长直到它已满,然后丢弃传入的客户端。因此,您通过排队获得的所有好处就是您只是延迟了第一个丢弃的客户端并将其他客户端搁置(这也很烦人)。

回到现实世界的例子。这个问题怎么能在这里解决?这很简单:加快处理每个客户或找一些文员,然后打开第二个、第三个……队列来一次处理多个客户并处理所有订单。就像在现实世界和编程中一样:加速并不是那么容易。仅在短时间内启动多个实例也很困难。但明确地说:这些是处理此类问题的唯一可靠解决方案。

使用 IIS 和 MVC:没有真正在正确的地方排队

[在单个队列上排队] 如果您已达到最大并发活动工作人员,您可以在应用程序中建立一个队列并暂停所有涌入的请求。但这并不意味着如果从 IIS 的角度来看,如果有空闲的并发连接,则在每个请求上产生和加载的所有涌入的 tcp 连接和 asp 会话没有开销,因为 IIS 接受新连接。

构建 http 代理应用程序

[在单个或多个队列上排队] 如果您正在构建自定义代理应用程序,您可以自行决定何时接受新的传入客户端连接(暂停 tcp 侦听器或不接受新客户端,直到您的工作负载足够处理新客户的资源)。当然,您的代理可以将请求传播到多个主机并进行某种负载平衡 - 但这需要您有额外的主机,您可以立即启动并处理请求(这是任何负载平衡器对您的要求:有一个后端可以处理工作量)。由于这是一个绝对低级的方法,我认为不需要进一步详细说明并解释走这条路有很多风险。

关于一些简单的方面来减少部分工作量: - 将图像等静态内容卸载到其他低成本的服务器/站点。这减少了您的主服务器上的工作负载,这些服务器执行艰苦的、动态的工作。- 缓存:不要做两次具有相同结果的事情。检查您的应用程序在高工作负载下哪里变慢,并尝试优化它们以尽可能快。- 异步工作/让长时间运行者进入睡眠状态:如果您有外部请求(数据库),请尝试以异步方式实现它们,以使您的线程尽可能长时间地运行,然后进入睡眠状态,让主机的调度程序处理其他待处理工作的机会。对您的线程进行基准测试,并检查您的关键方法的包含时间有多长以及它们将时间花在哪里。

希望这有助于解决您的问题,或者只是让您知道要走哪条路。

于 2013-07-17T19:39:39.113 回答
1

我建议您使用BIG-IP 之类的东西,如果您要使用基于软件的方法,那么您将在什么基础上Redirect向哪些服务器提出请求?有很多选择,例如Round Robin ..etc

看看这篇文章是否有帮助

于 2013-07-24T09:51:05.297 回答
0

一些选项是使用 sql 服务代理或复制快照。使用快照,您可以将报告过程移动到数据库的其他副本并保持低锁定。

于 2013-07-24T15:18:35.443 回答
0

您可以查看一些基准数据,以了解您当前的设置(基础设施和应用程序)可以处理什么样的负载/流量,例如有多少并发用户等。

您可以使用 Microsoft Team Foundation Service(托管在云中,不同于托管在本地即您的基础架构中的 TF Server)来执行此操作。使用负载/压力/性能测试

我相信 TFS 服务对于我认为少于 5 个开发人员的开发团队是免费的。不要引用我的话:-)

一旦你得到一些球场数据,如果你的应用程序可以应付,那么什么都做。如果它不能那么做一些决定?您当时处于 80/20 情景中。是否值得将 80% 的资源(开发时间、工资等)用于可能的 20% 性能提升,或者投入 20% 的硬件成本(CPU、RAM、另一台服务器等)以立即提高 80% 的性能是否更好?

如果此站点对您的企业或客户来说知名度很高,并且正在产生收入或具有立法目的,那么您可能会发现投资硬件比投资软件开发更容易。这样,您就可以拥有一个没有单点故障 (SPF) 的高可用性 (HA) 可扩展应用程序。

并没有真正回答您的排队问题,而只是要考虑的另一个观点。

仅供参考,我们希望升级到两个运行 windows server 2012 企业版的虚拟机(运行 VMWare)前端网络服务器。这些将使用 microsoft NLB 进行网络负载平衡(因此不需要其他负载平衡服务器或第三方软件)。我们将为支持的 SQL 服务器创建一个主动-主动集群。这使我们能够在不停机的情况下维护服务器(升级、补丁等)。另一个好处是使用 IIS 8.0,您可以使用 SNI 为 SSL (HTTPS) 流量运行相同 IP 的多个域(主机),这很好(但鱼雷用户运行 windows xp,但无论如何它在 2014 年 4 月不支持)。我们必须升级我们的 .net 应用程序来处理会话管理和 MAC 状态(跨服务器回发等),但交易是值得的。

于 2013-07-17T19:31:30.003 回答
0

随着 PKKG 的回答,您将不得不重新设计应用程序的某些部分以添加缓存。

ASP.NET MVC 输出缓存支持SQL 缓存依赖关系,它允许您根据表中的某些版本列由服务器和客户端缓存页面。

假设您有一个页面显示频率很高,但更改频率较低。例如,堆栈溢出的问题页面。每个问题可能有 1:100 的写入与读取操作。这意味着对于每次读取,连接到数据库的成本非常高。

所以我们可以做的是,将 LostUpdate 列视为版本列,并在此页面上添加 SQL 缓存依赖关系,并使用返回 true 的 SQL 查询如果当前问题的 LastUpdate 晚于给定的缓存时间。

对于每一个页面,缓存管理器都会对 SQL 进行缓存查询,但是这个查询比整个页面要小,因为页面还包含对不同表的大量查询,以构建和显示每个信息。例如,答案,以及他们的作者和他们的声誉等。因此,对于 100 次,只有一次对 LostUpdate 的查询可以节省大量开销,而不是 100 次所有多个查询和连接以及许多其他计算。

在每个页面上执行此操作很困难,但您可以分析流量模式并改进缓存。在与 Stake Overflow 相同的示例中,在主页上缓存可能不是一个好主意,因为它会显示最近更新的条目,因此缓存可能不起作用,因为页面变化太快。

于 2013-07-21T19:59:56.170 回答