可伸缩性

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

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

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

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

RPC 线程模型

当服务器收到 RPC 调用时,会在 RPC 提供的线程上调用服务器例程(管理器例程)。 RPC 使用随着工作负荷波动而增加和减少的自适应线程池。 从 Windows 2000 开始,RPC 线程池的核心是完成端口。 RPC 的完成端口及其使用情况已针对零到低争用服务器例程进行了优化。 这意味着,如果某些线程被阻止,RPC 线程池会主动增加服务线程的数量。 它针对假设阻塞是罕见的,如果线程被阻止,则这是一个快速解决的临时条件。 此方法可实现低争用服务器的效率。 例如,在通过高速系统区域网络(SAN)访问的八处理器 550MHz 服务器上运行的 void 调用 RPC 服务器每秒从 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,则可以将 RPC 配置为使用网络负载平衡(NLB),以便给定客户端地址的所有请求都转到同一服务器。 由于每个 RPC 客户端都打开了连接池(有关详细信息,请参阅 RPC 和网络),因此从给定客户端池的所有连接最终都位于同一服务器计算机上至关重要。 只要满足此条件,就可以将 NLB 群集配置为充当具有潜在出色可伸缩性的大型 RPC 服务器。