可伸缩性

术语“可伸缩性”经常被滥用。 对于本部分,提供了双重定义:

  • 可伸缩性是充分利用多处理器系统 (2、4、8、32 或更多处理器) 的可用处理能力的能力。
  • 可伸缩性是为大量客户端提供服务的能力。

这两个相关的定义通常称为 纵向扩展。 本主题的末尾提供了有关 横向扩展的提示。

本讨论仅侧重于编写可缩放服务器,而不是可缩放客户端,因为可缩放服务器是更常见的要求。 本部分还仅讨论 RPC 和 RPC 服务器上下文中的可伸缩性。 此处不讨论可伸缩性的最佳做法,例如减少争用、避免全局内存位置上的频繁缓存缺失或避免错误共享。

RPC 线程模型

当服务器收到 RPC 调用时,会在 RPC 提供的线程上调用服务器例程 (管理器例程) 。 RPC 使用随着工作负载波动而增加和减少的自适应线程池。 从 Windows 2000 开始,RPC 线程池的核心是完成端口。 RPC 针对零到低争用服务器例程优化完成端口及其用法。 这意味着,如果某个线程被阻止,RPC 线程池会主动增加服务线程的数量。 它基于以下假设进行操作:阻塞很少见,如果线程被阻止,则这是一个可快速解决的临时情况。 此方法可实现低争用服务器的效率。 例如,void 调用通过高速系统区域网络访问的 8 个处理器 550MHz 服务器上的 RPC 服务器, (SAN) 每秒从 200 多个远程客户端处理超过 30,000 个 void 调用。 这表示每小时超过 1.08 亿次呼叫。

结果是,当服务器上的争用量很高时,主动线程池实际上会妨碍该线程池。 为了说明,假设有一个用于远程访问文件的重载服务器。 假设服务器采用最简单的方法:只需在 RPC 调用服务器例程的线程上同步读取/写入文件。 此外,假设我们有一个四处理器服务器,为许多客户端提供服务。

服务器将从五个线程开始, (这实际上有所不同,但为简单起见,) 使用五个线程。 RPC 选取第一个 RPC 调用后,它会调度对服务器例程的调用,服务器例程会发出 I/O。 它很少会错过文件缓存,然后阻止等待结果。 一旦它阻止,就会释放第五个线程来选取请求,第六个线程将创建为热备用线程。 假设每十个 I/O 操作会错过缓存, (任意时间值) 阻塞 100 毫秒,并且假设四个处理器服务器每秒处理大约 20,000 个调用, (每个处理器) 5,000 个调用,则简化的建模将预测每个处理器将生成大约 50 个线程。 这假定每 2 毫秒将阻止一次调用,100 毫秒后,第一个线程将再次释放,因此池将稳定在大约 200 个线程 (每个处理器) 50 个线程。

实际行为更为复杂,因为线程数高会导致额外的上下文切换,从而减慢服务器速度,并降低新线程的创建速度,但基本思路是明确的。 当服务器上的线程开始阻止并等待某些 (I/O 或访问资源) 时,线程数会迅速增加。

RPC 和入口传入请求的完成端口将尝试将服务器中可用 RPC 线程数保持为等于计算机上的处理器数。 这意味着,在四处理器服务器上,一旦线程返回到 RPC,如果有四个或更多个可用 RPC 线程,则不允许第五个线程选取新请求,而是会处于热待机状态,以防当前可用线程阻止其中一个线程。 如果第五个线程作为热备用服务器等待的时间足够长,而可用 RPC 线程数低于处理器数,则会释放该线程,即线程池将减少。

假设一个服务器有多个线程。 如前所述,RPC 服务器最终会包含许多线程,但前提是线程经常阻塞。 在线程经常阻止的服务器上,返回 RPC 的线程很快会从热备用列表中删除,因为所有当前可用线程都会被阻止,并且会发出处理请求。 当线程阻止时,内核中的线程调度程序会将上下文切换到另一个线程。 此上下文切换本身会消耗 CPU 周期。 下一个线程将执行不同的代码,访问不同的数据结构,并且将具有不同的堆栈,这意味着 (L1 和 L2 缓存) 的内存缓存命中率将低得多,从而导致执行速度变慢。 同时执行的多个线程会增加现有资源的争用,例如堆、服务器代码中的关键部分等。 这进一步增加了作为车队对资源形式的争用。 如果内存不足,则大量且不断增加的线程所施加的内存压力将导致页面错误,从而进一步增加线程阻塞的速率,并导致创建更多线程。 根据它阻止的频率和可用的物理内存量,服务器可能稳定在一些较低级别的性能与较高的上下文切换速率,或者它可能会恶化到它只重复访问硬盘和上下文切换而不执行任何实际工作的程度。 当然,这种情况在轻工作负载下不会显示,但繁重的工作负载会很快将问题浮出水面。

如何防止这种情况发生? 如果预期线程要阻止,请将调用声明为异步调用,并在请求进入服务器例程后,将其排队到使用 I/O 系统和/或 RPC 异步功能的工作线程池。 如果服务器反过来进行 RPC 调用,则使这些调用变得异步,并确保队列不会变得太大。 如果服务器例程正在执行文件 I/O,请使用异步文件 I/O 将多个请求排到 I/O 系统的队列中,并且只有几个线程将它们排队并选取结果。 如果服务器例程正在执行网络 I/O,请再次使用系统的异步功能发出请求并选取答复,并尽可能少地使用线程。 完成 I/O 或服务器进行的 RPC 调用完成后,请完成传递请求的异步 RPC 调用。 这将使服务器能够使用尽可能少的线程运行,从而提高性能和服务器可以服务的客户端数。

Scale Out

如果将 NLB 配置为使用网络负载均衡 (NLB) ,则可以将 RPC 配置为使用,以便来自给定客户端地址的所有请求都流向同一服务器。 由于每个 RPC 客户端 (打开一个连接池,有关详细信息,请参阅 RPC 和网络) ,因此,来自给定客户端池的所有连接必须最终位于同一服务器计算机上。 只要满足此条件,NLB 群集就可以配置为具有潜在出色可伸缩性的大型 RPC 服务器。