Поделиться через


Масштабируемость

Термин, масштабируемость часто используется неправильно. В этом разделе представлено двойное определение:

  • Масштабируемость — это возможность полностью использовать доступную мощность обработки в многопроцессорной системе (2, 4, 8, 32 или более процессоров).
  • Масштабируемость — это возможность обслуживания большого количества клиентов.

Эти два связанных определения обычно называются масштабированием. В конце этого раздела приведены советы по масштабированию.

В этом обсуждении основное внимание уделяется написанию масштабируемых серверов, а не масштабируемых клиентов, так как масштабируемые серверы являются более распространенными требованиями. В этом разделе также рассматривается масштабируемость только в контексте серверов RPC и RPC. Рекомендации по масштабируемости, такие как сокращение конфликтов, предотвращение частых промахов кэша в глобальных расположениях памяти или предотвращение ложного общего доступа, не рассматриваются здесь.

Модель потоков RPC

Когда вызов RPC получается сервером, подпрограмма сервера (подпрограмма диспетчера) вызывается в потоке, предоставленном RPC. RPC использует адаптивный пул потоков, который увеличивает и уменьшается по мере колебания рабочей нагрузки. Начиная с Windows 2000 основной частью пула потоков RPC является порт завершения. Порт завершения и его использование RPC настраиваются на ноль до низкой процедуры сервера состязания. Это означает, что пул потоков RPC агрессивно увеличивает количество потоков обслуживания, если некоторые становятся заблокированными. Он работает с предположением, что блокировка редко, и если поток блокируется, это временное условие, которое быстро разрешается. Этот подход обеспечивает эффективность для серверов с низким уровнем состязаний. Например, сервер RPC, работающий на сервере с восемью процессорами 550 МГц, доступ к ним осуществлялся через сеть зоны системы высокой скорости (SAN), обслуживает более 30 000 вызовов void в секунду с более чем 200 удаленных клиентов. Это составляет более 108 миллионов звонков в час.

Результатом является то, что агрессивный пул потоков на самом деле получается таким образом, когда спор на сервере высок. Чтобы проиллюстрировать, представьте, что сервер с высокой нагрузкой, используемый для удаленного доступа к файлам. Предположим, что сервер использует самый простой подход: он просто считывает или записывает файл синхронно в потоке, на котором RPC вызывает подпрограмму сервера. Кроме того, предположим, что у нас есть четырехпроцессорный сервер, обслуживающий множество клиентов.

Сервер будет начинаться с пяти потоков (это на самом деле зависит, но пять потоков используются для простоты). После того как RPC берет первый вызов RPC, он отправляет вызов в подпрограмму сервера, а подпрограмма сервера выдает проблемы ввода-вывода. Редко он пропускает кэш файлов, а затем блокирует ожидание результата. Как только он блокируется, пятый поток освобождается для получения запроса, а шестой поток создается как горячий резервный. Предполагая, что каждая десятая операция ввода-вывода пропускает кэш и блокируется для 100 миллисекунд (произвольное значение времени), и предполагается, что четырехпроцессорный сервер обслуживает около 20 000 вызовов в секунду (5000 вызовов на процессор), упрощенное моделирование будет прогнозировать, что каждый процессор будет вызывать примерно 50 потоков. Предполагается, что вызов, который будет блокироваться каждые 2 миллисекунда, и после 100 миллисекунд первый поток освобождается снова, чтобы пул стабилизировался примерно на 200 потоков (50 на процессор).

Фактическое поведение сложнее, так как большое количество потоков приведет к дополнительным переключениям контекста, которые замедляют сервер, а также замедляет скорость создания новых потоков, но основная идея понятна. Количество потоков быстро повышается, так как потоки на сервере начинают блокироваться и ждать чего-то (будь то ввода-вывода или доступ к ресурсу).

RPC и порт завершения, входящий запросы будут пытаться поддерживать количество используемых потоков RPC на сервере, равное количеству процессоров на компьютере. Это означает, что на четырехпроцессорном сервере после возвращения потока в RPC, если существует четыре или более используемых потоков RPC, пятый поток не может получить новый запрос, и вместо этого будет находиться в состоянии горячего ожидания в случае одного из доступных в настоящее время блоков потоков. Если пятый поток ожидает достаточно долго, как горячий резервный без количества используемых потоков RPC снижается ниже количества процессоров, то есть пул потоков уменьшится.

Представьте себе сервер с множеством потоков. Как упоминалось ранее, сервер RPC заканчивается множеством потоков, но только в том случае, если потоки блокируются часто. На сервере, где потоки часто блокируются, поток, возвращающийся в RPC, скоро удаляется из списка горячего резервирования, так как все доступные для использования потоки блокируются и получает запрос на обработку. Когда поток блокируется, диспетчер потоков в ядре переключает контекст на другой поток. Этот параметр контекста сам по себе потребляет циклы ЦП. Следующий поток будет выполнять другой код, доступ к разным структурам данных и будет иметь другой стек, что означает, что скорость попадания кэша памяти (кэши L1 и L2) будет значительно ниже, что приведет к замедлению выполнения. Многочисленные потоки, выполняемые одновременно, увеличивают результаты для существующих ресурсов, таких как куча, критически важные разделы в коде сервера и т. д. Это еще больше увеличивает состязание по мере конвоев в форме ресурсов. Если память низка, давление на память, которое оказывает большое и растущее число потоков, приведет к сбоям страниц, что увеличивает скорость, с которой блок потоков, и приводит к еще большему количеству потоков. В зависимости от того, сколько времени он блокирует и сколько физической памяти доступно, сервер может стабилизироваться на некотором более низком уровне производительности с высокой скоростью переключения контекста или может ухудшаться до точки, когда он только многократно обращается к жесткому диску и переключению контекста без выполнения каких-либо фактических работ. Эта ситуация не будет отображаться под легкой рабочей нагрузкой, но тяжелая рабочая нагрузка быстро приносит проблему на поверхность.

Как это можно предотвратить? Если потоки должны блокироваться, объявите вызовы как асинхронные и после ввода запроса в подпрограмму сервера в очереди в пул рабочих потоков, которые используют асинхронные возможности системы ввода-вывода и /или RPC. Если сервер в свою очередь делает вызовы RPC сделать эти асинхронные, и убедитесь, что очередь не растет слишком большой. Если подпрограмма сервера выполняет операции ввода-вывода файлов, используйте асинхронный ввод-вывод файла для очереди нескольких запросов в систему ввода-вывода и только несколько потоков в очереди и забрать результаты. Если подпрограмма сервера выполняет сетевые операции ввода-вывода, используйте асинхронные возможности системы, чтобы выдавать запросы и асинхронно собирать ответы и использовать как можно меньше потоков. После завершения ввода-вывода или вызова RPC, выполняемого сервером, завершите асинхронный вызов RPC, доставляющий запрос. Это позволит серверу работать с максимально небольшими потоками, что повышает производительность и количество клиентов, которые могут обслуживать сервер.

Горизонтальное масштабирование

RPC можно настроить для работы с балансировкой сетевой нагрузки (NLB), если NLB настроен таким образом, чтобы все запросы с заданного адреса клиента пошли на тот же сервер. Так как каждый клиент RPC открывает пул подключений (дополнительные сведения см. в разделе RPC и сетевой), важно, чтобы все подключения из пула данного клиента заканчивались на одном серверном компьютере. Если это условие выполнено, кластер NLB можно настроить для работы как один большой сервер RPC с потенциально отличной масштабируемостью.