服务中处理传入请求的架构

3
我正在设计一个服务器守护程序,用于处理大量并发请求并进行异步处理。我意识到这样的项目规模非常庞大,但我很认真,并在进一步操作之前尝试制定清晰的设计和计划。
以下是我的目标列表:
- 可扩展性 - 必须能够将架构并行化到多个处理器甚至多个服务器上。 - 能够处理大量并发连接。 - 如果单个请求需要长时间处理,则不得引起阻塞问题。 - 请求到响应的反应时间必须最小化。 - 基于.NET框架构建(将使用C#编写)。
我提出的架构和流程相当复杂,因此这里是我最初设计的图表:

Architecture Flow Chart

(如果图片调整不好,在这里看

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

每个队列对应于一个线程,其亲和性设置为服务器上的一个CPU核心。这些线程是并行请求处理器的一部分,从每个队列消耗请求。请求分为三种类型之一:

  1. 立即 - 立即请求是按照名称所示,立即处理的。

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

  3. 定时 - 定时请求与目标时间戳一起放入定时请求队列(TRQ)中。这些请求通常是作为另一个请求的结果生成的,而不是由客户端显式发送的。当请求时间戳超过时,下一个可用的请求处理线程会消耗并处理它。

处理请求时,数据可以从内存中的键/值对缓存、键/值对缓存或磁盘上获取,也可以从专用的SQL数据库服务器获取。缓存的值将是BSON,索引将是字符串。我考虑使用 Dictionary<T1,T2> 在内存中实现这个功能,并使用B树(或类似的东西)来进行磁盘缓存。

处理完成后,响应会被创建并放置到输出队列(OPQ)中。然后一个循环会从OPQ中消耗响应,并通过网络将其传输回客户端。如果OPQ达到其最大大小的80%,则会停止四分之一的请求处理器线程。如果OPQ达到其最大大小的90%,则会停止一半的请求处理器线程。如果OPQ达到其最大大小,则所有请求处理器线程都会停止。这将使用信号量实现,该信号量还应防止单个请求处理器线程被阻塞并留下过时的请求。
我正在寻找的建议涉及以下几个方面:
- 我是否忽略了任何此架构的主要缺陷? - 是否有任何性能方面需要考虑更改的内容? - 对于请求,TCP或UDP更合适?拥有TCP提供的“交付证明”非常有用,但UDP的轻量级特性也很吸引人。 - 在Windows服务器上处理100k+同时连接时,是否需要考虑特殊因素?我知道Linux的TCP堆栈处理得很好,但对于Windows,我不太确定。 - 是否有其他问题需要问?我是否忘记考虑任何事情?
我知道这篇文章很长,而且可能要求也比较多,所以感谢您的时间。

这里是更新后的图示链接在此


这个项目进行得如何?有关于它的博客文章吗?我非常想听听你在这个过程中学到了什么以及得出了什么结论。 - Tyson
3个回答

2

您还可以考虑以下几点:

  • 故障转移。您可以设计一种方法,在可能出现服务崩溃的情况下保持请求的持久性,以便在服务重新启动后仍将处理所有挂起的请求。
  • 错误队列。(也称为Dead Letter Channel模式)
  • 管道和过滤器。通过提供此功能,您可以实现服务的高度灵活性和可扩展性。
  • 请求确认。在预定义的时间间隔内,客户端会向等待Ack消息的服务发送具有设置为初始RequestId的CorrelationId的请求,这样服务就可以通知客户端已接收并将特定请求放置在入站队列中,如果客户端未收到刚发送的请求的Ack,则可以重新发送或标记为失败。

附注:我还建议阅读优秀的书籍 "企业集成模式"。


故障转移方面的观点很好。由于每个实例都在单独的服务器上运行,我打算在它们之间进行负载均衡(无论请求发送到哪个实例),并在服务器崩溃时重新平衡。然而,我没有考虑过对待挂起请求的想法。也许我应该在磁盘上保留它们的副本以防止守护程序崩溃,但如果整个机器崩溃,则认为请求已丢失?有更好的想法吗? - Polynomial
只是为了澄清:所需的响应时间意味着如果实际服务器崩溃,重启时间将太长,以至于请求仍然相关,因此我必须放弃磁盘队列。 - Polynomial

1

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

至于TCP vs. UDP-您正在寻找什么样的性能?使用一些现有的通信基础设施(如ZeroMQ)来为您处理这些技术细节是否更好呢?

Itay。


拥有多个请求队列的想法源于我对某些队列进行专门化,以优先处理某些请求类型的想法。这应该允许我调整我的代码,使得处理某些消息类型变得更快一些。至于TCP/UDP/其他方面,我不太确定我需要什么。我希望直接与网络协议进行交互,但这意味着我受限于.NET支持的内容(几乎只有TCP和UDP)。 - Polynomial
我不会去那里。如果你的队列支持优先级(一些队列系统支持,我不记得MSMQ是否支持),那么你就可以轻松搞定了。总的来说,我认为你正在试图重新发明已经被发明并开源的很多东西。在开始通过网络实现事物之前,我真的建议你仔细研究几个现有的队列系统。同时也可以看看WCF,虽然我不确定它的性能如何。 - zmbq
也许我应该保留多个队列,但只是用它们来存储不同的消息优先级。这样我就可以将负载均衡器移动到队列的另一侧,并简化很多内部工作流程。 - Polynomial
多个队列 - 每个队列对应一个优先级 - 是在不支持优先级的队列系统中实现几个优先级的常见方法。确实,它提供了更好的可扩展性所需的解耦。你正在使用哪些队列? - zmbq
我正在使用 .NET 框架中的 Queue<T>,它不支持优先级。我将使用加权随机数来负载均衡获取操作,就像我之前计划用于插入操作一样。更新: 如果需要明确的线程安全,则可能最终使用 ConcurrentQueue<T> - Polynomial
这是一个更新的图表版本,以对应设计更改,基于您的建议:http://i43.tinypic.com/w6t7r4.png - Polynomial

0
如果你想要这个系统能够很好地扩展,你需要确保所有组件都是可扩展的——处理元素、输入/输出部分和队列。如果你打算在Microsoft平台上实现这一点,我强烈建议你考虑使用Windows Azure,它提供了大多数(如果不是全部)你所需的关键功能。你没有提到的一件事——是否会有一个持久化存储层(例如数据库)?如果有,那么请准备好将其扩展,否则它将成为你的单点故障。

数据库在图表中显示并在我的问题中提到。我也不想使用Azure,因为我更希望让我的应用程序执行逻辑。原因是我希望它可以安装在各种不同的主机上(包括客户主机),并且让它们作为自己的实例或共享实例的一部分。 - Polynomial
抱歉 - 由于我当前的位置被阻止,我错过了帖子中的DB参考。至于“我的应用程序执行逻辑”,我不明白使用Azure的可扩展性功能如何削弱您调整逻辑的能力。安装在客户主机上的应用程序可以使用“公共”实例,或使用单独的帐户来“私有化”其安装。 - Harper Shelby
我的意思是,我希望某些客户能够在不购买或安装Azure的情况下运行自己的服务器“农场”。我也希望自己远离昂贵的软件依赖。 - Polynomial

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接