В этом эссе обсуждаются некоторые практические советы по настройке очереди задач и рабочего пула.

Фон

Очередь задач — распространенный инструмент для формирования трафика. По сути, очередь задач действует как буфер между запросами (задачами) и процессорами, чтобы сгладить прерывистые тяжелые (импульсные) нагрузки. Такое формирование трафика имеет несколько важных преимуществ:

  • Максимизируйте пропускную способность системы за счет сокращения ненужных накладных расходов (например, переключения контекста).
  • Защитите серверные ресурсы, так как пакетные нагрузки могут привести к (1) сбою или даже сбою процессов/служб (например, OOM) или (2) тайм-ауту запросов из-за медленного ответа. Этот аспект часто упускают из виду!
  • Сделать поведение системы более предсказуемым и понятным, и, следовательно, обеспечить оптимальную конфигурацию (вместо догадок).

Как правило, очередь задач используется вместе с рабочим пулом в схеме производитель-потребитель, как показано ниже.

С очередью задач легко применить принцип «сбой быстро, сбой раньше».

  • Логика постановки в очередь: если очередь заполнена, отбросить запрос и немедленно вернуть клиенту сообщение об ошибке. Ошибка раньше!
  • Логика удаления из очереди: проверьте, достаточно ли времени для обработки запроса; если нет, отбросить и вернуть ошибку клиенту. Ошибка быстро!

Занятость очереди

Давайте посмотрим, в каком состоянии может находиться очередь в зависимости от объема трафика.

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

Параметры дизайна

Теперь перейдем к основной теме этого эссе: как настроить очередь задач и рабочий пул? Необходимо внимательно отнестись к следующим параметрам:

  • Количество рабочих
  • Длина очереди
  • Тайм-аут клиента (только для синхронного типа запрос-ответ)

Вот правда ли, что чем больше, тем веселее? То есть должны ли мы всегда стремиться к тому, чтобы очереди были длиннее, а работников — больше?

Количество рабочих

Это зависит от характера приложения. Для приложений, связанных с вычислениями, количество рабочих процессов должно быть прямо пропорционально количеству ядер ЦП; Я видел предложения установить удвоенное количество ядер ЦП. Например, если вы работаете на 16-ядерном компьютере, вы можете установить 32 рабочих процесса.

Для привязанных к вводу-выводу приложений это зависит от пропускной способности жесткого диска. Мой собственный опыт показывает, что 10-20 одновременных потоков ввода-вывода могут максимизировать пропускную способность жесткого диска.

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

Длина очереди и время ожидания клиента

Эти два параметра тесно связаны и должны удовлетворять некоторым ограничениям.

  • avg_proc_delay (он же 1/throughput_per_worker): среднее время, необходимое для обработки задачи (после удаления из очереди).
  • max_wait_in_queue: при удалении задачи из очереди отбрасывать задачу, если время ожидания › max_wait_in_queue
  • client_timeout: ~= max_wait_in_queue + avg_proc_delay + network_delay
  • queue_length: удовлетворяет queue_length / (num_workers * throughput_per_worker) ‹ client_timeout

Давайте используем один пример, чтобы проиллюстрировать, как получить правильные значения. Представьте себе приложение, связанное с вычислениями, работающее на 24-ядерном сервере. Предположить, что

  • throughput_per_worker = 100 запросов в секунду (он же avg_proc_delay = 10 мс)
  • num_workers = 24 (т. е. столько же, сколько ядер)
  • client_timeout = 5 секунд (произвольно, но разумно)

Тогда у нас было бы

max_wait_in_queue ~= 5сек-10мс = 4,99сек (без учета network_delay!), и

queue_length~= 4,99 * (100*24) = 11 976.

Заворачивать

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

Вот и все. Спасибо за чтение!