Рабочие пула потоков перегружены Runnables, разбивающими JVM - PullRequest
1 голос
/ 09 ноября 2011

Brief

Я использую многопоточный tcp-сервер, который использует фиксированный пул потоков с неограниченной очередью Runnable. Клиенты отправляют исполняемые файлы в пул.

В моем сценарии стресс-теста 600 клиентов пытаются войти на сервер и сразу же рассылают сообщения всем другим клиентам одновременно и несколько раз без конца и без сна (сейчас клиенты просто отбрасывают входящие сообщения). Используя четырехъядерный процессор с 1 ГБ, зарезервированным для динамической памяти, и параллельный сборщик мусора как для молодого, так и для старого поколений, сервер падает с исключением OOM через 20 минут. Наблюдение за сборщиком мусора показывает, что постоянное поколение медленно увеличивается, а полный сборщик мусора освобождает лишь небольшую часть памяти. Снимок полной кучи показывает, что старое поколение почти полностью занято Runnables (и их исходящими ссылками).

Кажется, что рабочие потоки не могут завершить выполнение Runnables быстрее, чем клиенты могут поставить их в очередь для выполнения (для каждого входящего "события" на сервер, сервер создаст 599 runnables, так как их 600 - 1 клиенты - при условии, что они все вошли в систему в то время).

Вопрос

Может кто-нибудь помочь мне разработать стратегию работы с перегруженными рабочими пула потоков?

Также

  • Если я ограничу очередь, какую политику я должен реализовать для обработки отклоненного выполнения?
  • Если я увеличу размер кучи, разве это только продлит исключение OOM?
  • Можно сделать расчет для измерения объема работы, выполненной в агрегации Runnables. Может быть, это измерение будет использоваться в качестве основы для механизма блокировки для координации диспетчерской работы клиентов?
  • Какую реакцию должен испытать клиент, когда сервер перегружен работой?

Ответы [ 4 ]

2 голосов
/ 09 ноября 2011

Я бы посоветовал вам ограничить емкость очереди и «отодвинуть» издателя, чтобы остановить его публикацию или изящно отбросить запросы. Вы можете сделать первый b, сделав блок очереди, когда он заполнится.

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

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

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

Кстати: если вы используете прямые ByteBuffers, вы можете сделать это без создания мусора и с минимумом кучи. например ~ 1 КБ кучи на клиента.

2 голосов
/ 09 ноября 2011

1) Не используйте неограниченную очередь.Я не могу сказать вам, какой должна быть граница;ваши нагрузочные тесты должны дать вам ответ на этот вопрос.В любом случае, сделайте границу настраиваемой: по крайней мере, настраиваемую динамически, лучше, но адаптируемую к некоторым измерениям нагрузки.

2) Вы не сказали нам, как клиенты отправляют свои запросы, но если задействован HTTP, уже естькод состояния для перегруженного дела: 503 Сервис недоступен .

1 голос
/ 09 ноября 2011

Звучит так, как будто вы проводите нагрузочное тестирование.Я бы определил, что вы считаете "приемлемой тяжелой нагрузкой".Какой объем трафика вы можете ожидать от одного клиента?Тогда удвой это.Или втрое.Или масштабировать манеру, похожую на это.Используйте это пороговое значение, чтобы ограничить или запретить клиентам, использующим такую ​​большую пропускную способность.

Это имеет ряд льгот.Во-первых, он дает вам анализ, необходимый для определения нагрузки на сервер (количество пользователей на сервер).Во-вторых, это дает вам первую линию защиты от DDOS-атак.

1 голос
/ 09 ноября 2011

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

По сути, у вас есть 4 варианта:

  1. Заставьте клиентов ждать, пока вы не будете готовы принять их запросы
  2. Активно отклоняйте запросы клиентов, пока вы не будете готовы принимать новые запросы
  3. Разрешить клиентам тайм-аут при попытке связаться с вашим сервером, когда он не готов принимать запросы
  4. Смесь 2 или 3 из вышеуказанных стратегий.

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

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

Простая стратегия блокировки может быть реализована с помощью реализации BlockingQueue. Однако это не дает особенно мелкозернистого контроля.

Или вы можете использовать семафор для управления разрешениями на добавление задач в очередь, что имеет преимущество в предоставлении метода tryAcquire (long timeout, TimeUnit unit), если вы хотите применить умеренное регулирование.

В любом случае, не позволяйте потокам, обслуживающим клиентов, расти без границ, иначе вы просто получите OOM по другой причине!

...