【问题标题】:Architecture of processing incoming requests in a service在服务中处理传入请求的架构
【发布时间】:2011-12-21 14:14:22
【问题描述】:

我正在为一个必须同时处理大量请求并异步处理它们的项目设计一个服务器守护程序。我知道这样一个项目的规模之大,但我对此很认真,并且在继续前进之前正在努力制定清晰的设计和计划。

这是我的目标列表:

  • 可扩展性 - 必须能够将架构并行到多个处理器甚至多个服务器上。
  • 能够处理大量并行连接。
  • 如果单个请求需要很长时间处理,则不得导致阻塞问题。
  • 请求响应周转时间必须最短。
  • 围绕 .NET 框架构建(将用 C# 编写)

我提出的架构和流程相当复杂,所以这是我最初设计的图表:

(和here it is on tinypic,以防它严重调整大小)

这个想法是请求通过网络进入(尽管我还没有决定 TCP 还是 UDP 是最好的)并立即传递到高速负载平衡器。然后负载均衡器使用加权随机数生成器选择一个请求队列 (RQ) 来放置请求。权重来自每个队列的大小。使用加权 RNG 而不是仅仅将请求放入最不忙的队列的原因是,它可以防止空但阻塞的队列(由于挂起的请求)锁定整个服务器。如果所有 RQ 都超过一定大小,负载均衡器会丢弃请求并将“服务器太忙”响应放入输出队列 (OPQ) - 此部分未显示在图表中。 p>

每个队列对应一个线程,其关联性设置为服务器上的一个 CPU 核心。这些线程是并行请求处理器的一部分,它处理来自每个队列的请求。请求分为以下三种类型之一:

  1. 立即 - 顾名思义,立即处理请求。

  2. 可延迟 - 可延迟请求被视为低优先级。它们在低负载期间立即处理,或者如果负载高则放入延迟请求队列 (DRQ)。负载均衡器从 DRQ 获取这些延迟请求,将它们标记为立即,然后将它们放回适当的 RQ。

  3. 定时 - 定时请求连同它们的目标时间戳一起放入定时请求队列 (TRQ)。这些请求通常是由另一个请求生成的,而不是由客户端显式发送的。当超过请求时间戳时,下一个可用的请求处理器线程将使用它并处理它。

处理请求时,可能会从内存中的键/值对缓存、键/值对缓存或磁盘或专用 SQL 数据库服务器中获取数据。缓存的值是 BSON,索引是字符串。我正在考虑使用Dictionary<T1,T2> 在内存中实现这​​一点,并为磁盘缓存使用 btree(或类似的)。

处理完成后创建响应,并将其放入输出队列 (OPQ)。然后一个循环消耗来自 OPQ 的响应并通过网络将它们传输回客户端。如果 OPQ 达到其最大大小的 80%,则停止四分之一的请求处理器线程。如果 OPQ 达到其最大大小的 90%,则停止一半的请求处理器线程。如果 OPQ 达到其最大大小,则所有请求处理器线程都将停止。这将通过信号量来实现,它还应该防止单个请求处理器线程被阻塞并留下陈旧的请求。

我正在寻找的是关于几个方面的建议:

  • 此架构是否存在我遗漏的重大缺陷?
  • 出于性能原因,我应该考虑更改哪些内容?
  • TCP 还是 UDP 更适合请求?拥有 TCP 提供的“交付证明”会非常有用,但 UDP 的轻量级特性也很有吸引力。
  • 在 Windows 服务器上处理 100k+ 并发连接时,是否需要考虑任何特殊的注意事项?我知道 Linux 的 TCP 堆栈处理得很好,但我对 Windows 不太确定。
  • 我还有其他问题需要问吗?我是不是忘了考虑什么?

我知道要阅读的内容很多,可能还有很多问题要问,所以感谢您抽出宝贵的时间。

图表的更新版本here

【问题讨论】:

  • 这个项目进展如何/进展如何?有关于它的博客文章吗?我很想听听您在此过程中学到了什么以及得出了什么结论。

标签: .net networking architecture scalability parallel-processing


【解决方案1】:

如果您希望它很好地扩展,您需要确保所有组件都是可扩展的 - 处理元素、输入/输出片段和队列。如果您打算在 Microsoft 堆栈上执行此操作,我强烈建议您研究 Windows Azure,它提供了您需要的大部分(如果不是全部)关键功能。您没有提到的一件事 - 是否会有持久存储层(例如数据库)?如果是这样,请准备好扩展它,否则它将成为您的单点故障。

【讨论】:

  • 数据库显示在图表中,并在我的问题中提到。我也不想使用 Azure,因为我宁愿让我的应用程序执行逻辑。这样做的原因是我希望它可以安装在一系列不同的主机(包括客户主机)上,并让它们充当自己的实例或共享实例的一部分。
  • 抱歉 - 图像在我当前位置被阻止,我错过了帖子中的数据库参考。至于“我的应用程序执行逻辑”,我看不出使用 Azure 的可伸缩性功能如何剥夺您调整逻辑的能力。安装在客户主机上的应用程序可以使用“通用”实例,或使用单独的帐户将其安装“私有化”。
  • 我的意思是我希望某些客户能够运行他们自己的服务器“场”,而无需购买或安装 Azure。我自己也想远离昂贵的软件依赖。
【解决方案2】:

我不明白您为什么需要多个请求队列。在我看来,您只需要一个请求队列,许多处理器都从中读取。任何队列系统都应该没有问题。只有一个队列可以将输入与处理器分离,从而实现更好的可扩展性——在需要时启动更多处理器,其他人不需要关心它。

至于 TCP 与 UDP - 您在寻找什么样的性能?使用一些现有的通信基础设施(例如 ZeroMQ)来为您处理这些技术问题不是更好吗?

意大利。

【讨论】:

  • 拥有多个请求队列的想法源于我的一个想法,即专门化某些队列以偏爱请求类型的一个子集。这应该允许我调整我的代码以使处理某些消息类型的速度更快一些。在 TCP/UDP/其他方面,我不确定我需要什么。我想直接与网络协议交互,但这意味着我仅限于 .NET 支持的任何内容(几乎只是 TCP 和 UDP)。
  • 我不会去那里。如果您的队列支持优先级(某些队列系统支持,我不记得是否 MSMQ 支持),那么您就没有家了。总的来说,我认为您正在尝试重新发明很多已经发明的东西 - 并且是开源的。在我开始通过网络实现之前,我真的会研究几个现有的排队系统。也看看 WCF,虽然我不确定它是如何面向性能的。
  • 也许我应该保留多个队列,但仅使用它们来存储不同的消息优先级。这样我就可以将我的负载均衡器移到队列的另一端,并在此过程中简化很多内部工作。
  • 嗯,多个队列 - 每个优先级一个 - 是在不支持优先级的排队系统上实现一些优先级的一种非常常见的方式。事实上,它提供了更好的可扩展性所需的解耦。您使用的是哪些队列?
  • 我正在使用 .NET 框架中的 Queue<T>,它不支持优先级。我将使用加权 RNG 来对提取进行负载平衡,就像我之前计划对插入进行的那样。 更新:如果我需要明确的线程安全,我可能最终会使用 ConcurrentQueue<T>
【解决方案3】:

你也可以考虑以下:

  • 故障转移。您可以设计一种方法来在可能的服务崩溃时保留请求,以便即使在服务重新启动后也会处理所有待处理的请求
  • 错误队列。 (也称为Dead Letter Channel 模式)
  • Pipes and Filters。通过提供此类功能,您将实现服务的高度灵活性和可扩展性
  • 请求确认。在某个预定义的时间间隔内,向服务发送请求的客户端等待将 CorrelationId 设置为初始 RequestId 的 Ack 消息,这样服务可以通知客户端接收到特定请求并将其放入入站队列中,如果客户端没有接收刚刚发送的请求的 Ack - 它可以重新发送它或标记为失败。

PS:我还推荐一本好书“Enterprise Integration Patterns

【讨论】:

  • 这是故障转移的一个好点。由于每个实例都将在单独的服务器上运行,因此我将在它们之间进行负载平衡(无论将请求发送到哪一个),并且如果服务器出现故障,只需重新平衡即可。但是,我没有考虑对待处理的请求做某事的想法。也许我应该在磁盘上保留它们的副本以防守护程序崩溃,但是如果整台机器出现故障,请考虑丢失的请求?有更好的主意吗?
  • 只是为了澄清:所需的响应时间意味着如果实际服务器出现故障,重新启动时间将太长,请求仍然相关,所以我不得不扔掉无论如何都在磁盘队列中。
猜你喜欢
  • 1970-01-01
  • 2013-03-18
  • 2018-08-25
  • 2015-04-15
  • 1970-01-01
  • 2012-05-26
  • 2017-05-28
  • 2017-03-19
  • 2014-02-05
相关资源
最近更新 更多