Нитки сидят без дела = плохо?

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

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

Ответ 1

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

Вышеизложенное кажется явным выигрышем с точки зрения производительности, но асинхронная модель программирования намного сложнее в нескольких отношениях:

  • он не может быть выражен как один вызов функции, поэтому рабочий процесс не очевиден, особенно когда рассматривается передача управляющего потока из-за исключений;
  • без специально ориентированной поддержки с языка программирования, идиомы очень грязные: код спагетти и/или крайне слабое отношение сигнал/шум являются нормой;
  • в основном из-за 1. выше отладки гораздо сложнее, так как трассировка стека не отражает прогресс внутри единицы работы в целом;
  • выполнение переходов из потока в поток внутри пула (или даже нескольких пулов, где каждый уровень абстракции имеет свои собственные), поэтому профилирование и мониторинг с помощью обычных инструментов практически бесполезны.

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

  • адресное пространство огромно, поэтому пространство, зарезервированное для стеков, не является проблемой;
  • фактическая физическая загрузка ОЗУ стеков вызовов не очень велика, так как только часть стека, фактически используемая потоком, привязана к ОЗУ, а стек вызовов обычно не превышает 64 КБ; Переключение контекста
  • которое раньше было слишком дорогостоящим для большего количества потоков, было улучшено до такой степени, что его накладные расходы незначительны для всех практических целей.

Классическая статья, проходящая через многие из вышеперечисленных и некоторых других пунктов, является хорошим дополнением к тому, что я говорю здесь:

https://www.usenix.org/legacy/events/hotos03/tech/full_papers/vonbehren/vonbehren_html/index.html

Ответ 2

В комментариях к вашему вопросу уже есть несколько хороших указателей.

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

Пожалуйста, взгляните на стандарт websockets и асинхронную модель обработки запросов в стандарте Servlet 3.0. Все последние серверы веб-приложений Java теперь реализуют его (например, Glassfish и Tomcat), и это решение для вашей проблемы.

На сам вопрос не может быть дан ответ, так как отсутствует ОС, JVM и сервер приложений, которые вы используете. Однако вы можете протестировать его довольно быстро самостоятельно, просто создав сервлет или JSP с помощью Thread.sleep(9999999) и сделав siege -c 10000 ... на нем.

Ответ 3

10000 одновременных HTTP-клиентов... в чем проблемы при прохождении потоков?

Похоже, что стоимость незанятого потока - это только память, выделенная для структуры ядра (несколько kb) и стека потоков (512kb - число mb). Но...

Очевидно, вы будете время от времени пробуждать каждую из ваших n-сто потоков, не так ли? И это момент, когда вы оплачиваете стоимость переключения контекста, что может быть не так мало (время для вызова системного планировщика, больше промахов в кеше и т.д.). См., Например: http://www.cs.rochester.edu/u/cli/research/switch.pdf

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

И последнее слово. Мы не знаем, сколько раз ваши потоки будут заблокированы при чтении() и сколько работы они должны выполнить для обработки полученных данных. Какое оборудование, ОС и сетевые интерфейсы будут использоваться... Итак, проверьте прототип вашей системы.