编辑 我意识到我的问题表述得不够清楚,并对其进行了大量编辑。
这是一个开放式问题,所以提前道歉。
简而言之,我想在 Azure 辅助角色中实现 IIS 样式的异步请求处理。
它可能非常简单,也可能非常困难——我正在寻找指向何处研究的指针。
虽然我的实现将使用 Azure Worker 和服务总线队列,但一般原则适用于工作进程正在侦听传入请求并为其提供服务的任何场景。
IIS 的作用
在 IIS 中有一个固定大小的线程池。如果您同步处理所有请求,那么您可以并行处理的最大请求数 == maxthreads。但是,如果您必须通过缓慢的外部 I/O 来处理请求,那么这是非常低效的,因为您最终可能会导致服务器处于空闲状态,而所有线程都处于等待外部 I/O 完成的状态。
来自MSDN:
在 Web 服务器上,.NET Framework 维护一个线程池,用于为 ASP.NET 请求提供服务。当请求到达时,池中的一个线程被分派来处理该请求。如果请求是同步处理的,则处理请求的线程在处理请求时会被阻塞,并且该线程无法为另一个请求提供服务。
这可能不是问题,因为线程池可以足够大以容纳许多阻塞的线程。但是,线程池中的线程数是有限的。在处理多个同时长时间运行的请求的大型应用程序中,所有可用线程都可能被阻塞。这种情况称为线程饥饿。达到此条件时,Web 服务器将请求排队。如果请求队列已满,Web 服务器将拒绝 HTTP 503 状态(服务器太忙)的请求。
为了克服这个问题,IIS 有一些巧妙的逻辑允许您异步处理请求:
调用异步操作时,会发生以下步骤:
Web 服务器从线程池中获取一个线程(工作线程)并调度它来处理传入的请求。此工作线程启动异步操作。
工作线程返回到线程池以服务另一个 Web 请求。
当异步操作完成时,它会通知 ASP.NET。
Web 服务器从线程池(可能与启动异步操作的线程不同的线程)中获取一个工作线程来处理请求的其余部分,包括呈现响应。
这里的重点是当异步请求返回时,返回操作被安排在为初始传入请求提供服务的同一线程池之一上运行。这意味着系统限制了它同时执行的工作量,这就是我想要复制的内容。
我想做的事
我想创建一个 Worker 角色,它将在 Azure 服务总线队列上侦听传入的工作请求,也可能在 TCP 套接字上侦听。像 IIS 一样,我想拥有一个最大的线程池大小,并且我想限制工作人员并行执行的实际工作量;如果工作人员忙于服务现有请求 - 无论是新传入请求还是来自先前异步调用的回调 - 在某些线程被释放之前,我不想接收任何新传入请求。
限制我同时开始的工作数量不是问题——这很容易控制;它限制了我实际同时工作的数量。
让我们假设一个有 100 个线程的线程池。
我收到 100 个发送电子邮件的请求,每封电子邮件需要 5 秒才能发送到 SMTP 服务器。如果我将我的服务器限制为同时只处理 100 个请求,那么我的服务器将在 5 秒内无法执行任何其他操作,而 CPU 则完全空闲。所以,我并不介意同时发送 1,000 或 10,000 封电子邮件,因为 99% 的“请求处理时间”将用于等待外部 I/O,而我的服务器仍然非常安静。因此,我可以通过继续无限制地接受传入请求来处理这种特定情况(或者只限制请求的开始,直到我触发异步调用;一旦调用 BeginSend,我就会返回并开始服务另一个请求)。
现在,想象一下,我有一种类型的请求,它去数据库读取一些数据,对其进行一些繁重的计算,然后将其写回数据库。那里有两个数据库请求应该是异步的,但 90% 的请求处理时间将花在我的工作人员身上。因此,如果我遵循与上述相同的逻辑并保持启动异步调用并让返回做任何需要让线程继续运行的操作,那么我最终会得到一个非常过载的服务器。
不知何故,IIS 所做的是确保当异步调用返回时,它使用相同的固定大小的线程池。这意味着如果我触发大量异步调用,然后它们返回并开始使用我的线程,IIS 将不会接受新请求,直到这些返回完成。这是完美的,因为它确保了服务器上的合理负载,特别是当我有多个负载平衡的服务器和服务器从中挑选工作的队列系统时。
我有这种偷偷摸摸的怀疑,这可能很简单,我只是缺少一些基本的东西。或者,这可能非常困难。