Java в 2011 году: резьбовые сокеты VS NIO: что выбрать на 64-битной ОС и последней версии Java?

Я прочитал несколько сообщений о java.net и java.nio здесь, в StackOverflow, и в некоторых блогах. Но я все еще не могу понять, когда следует предпочесть NIO резьбовым сокетам. Не могли бы вы изучить мои выводы ниже и сказать, какие из них неверны, а какие упущены?

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

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

  • Пропускная способность NIO может быть ниже, потому что select () и poll (), используемые асинхронными библиотеками NIO в средах с высокой нагрузкой, дороже, чем пробуждение и перевод потоков в спящий режим.

  • NIO всегда был медленнее, но он позволяет обрабатывать больше одновременных подключений. По сути, это компромисс между временем и пространством: традиционный ввод-вывод быстрее, но требует большего объема памяти, NIO медленнее, но использует меньше ресурсов.

  • Java имеет жесткое ограничение на количество одновременных потоков 15000/30000 в зависимости от JVM, и это ограничит поток на модель подключения до этого максимального количества одновременных подключений, но JVM7 не будет иметь такого ограничения (не может подтвердить эти данные).

Итак, в качестве заключения можно получить следующее:

  • Если у вас десятки тысяч одновременных подключений - NIO - лучший выбор, если только скорость обработки запросов не является для вас ключевым фактором.
  • Если у вас меньше этого - поток на соединение является лучшим выбором (учитывая, что вы можете позволить себе объем ОЗУ для хранения стеков всех параллельных потоков до максимума)
  • С Java 7 вы можете захотеть перейти на NIO 2.0 в любом случае.

Я прав?


person Vladislav Rastrusny    schedule 25.03.2011    source источник


Ответы (5)


Мне это кажется правильным, за исключением части о Java, ограничивающей количество потоков, которая обычно ограничивается ОС, в которой она работает (см. Сколько потоков может поддерживать виртуальная машина Java? и Невозможно пройти 2542 потоков в Java на 4GB iMac OSX 10.6.3 Snow Leopard (32bit )).

Чтобы достичь такого количества потоков, вам, вероятно, потребуется настроить размер стека JVM.

person Adam Bryzak    schedule 25.03.2011
comment
Также следует отметить, что если вы используете все эти подключения для связи с сервером (т.е. вы являетесь клиентской стороной подключения), вы ограничены примерно 65 000 подключений из-за количества доступных локальных портов. - person Adam Bryzak; 25.03.2011
comment
Нет. Каждый клиент подключается к одному и тому же порту. Кстати, я беру эти темы, поскольку нет жесткого ограничения? Что говорит здесь Пол Тума: paultyma.blogspot.com/2008 / 03 / - person Vladislav Rastrusny; 25.03.2011
comment
Они подключаются к одному и тому же порту на стороне сервера, но для клиентских подключений по-прежнему необходимо выделять порт локально для получения ответа от сервера. - person Adam Bryzak; 25.03.2011
comment
Это не может быть правдой, потому что в этом случае это будет ложь: groovy.dzone.com/ статьи / 512000-одновременных-веб-сокетов | coverant.net/product/soapboxserver.aspx: они оба сообщают об одновременных подключениях к одиночная машина. - person Vladislav Rastrusny; 25.03.2011
comment
Если вы настроите машину с несколькими IP-адресами (либо через виртуальные интерфейсы, либо через физические сетевые адаптеры), каждый из них сможет иметь ~ 65 000 клиентских подключений, извините за то, что не уточнил это в моем первом комментарии. В первой опубликованной вами ссылке упоминается, что их клиентская программа также открывает 64 000 соединений. - person Adam Bryzak; 25.03.2011
comment
Я считаю это неправильным. Насколько я знаю, соединение в TCP определяется четверкой {host1, port1; хост2, порт2}. Если вы открываете прослушивающий сокет, локальный порт не назначается для обработки подключающегося клиента. Но если вы открываете исходящее соединение с удаленным сервером, действительно, локальный порт назначается для обработки вновь открытого соединения. Итак, они открыли по 64000 подключений с каждой машины, чтобы не выходить из числа доступных локальных портов, поддерживающих подключение со стороны клиента. - person Vladislav Rastrusny; 26.03.2011
comment
@FractalizeR: Это правильно. Каждое исходящее соединение требует уникального номера порта в текущих реализациях TCP. Теоретически стек TCP / IP может заметить кортеж при выделении исходящего порта; на практике это невозможно из-за API: в частности, из-за того, что bind () происходит перед connect (), явно или неявно. - person user207421; 28.03.2011
comment
Теоретически вы можете использовать один и тот же локальный порт для многих исходящих подключений, но я не уверен, что API для этого есть. TCP действительно использует локальный И удаленный порт, чтобы решить, какой сокет используется. Я считаю, что вместо этого люди создают несколько локальных IP-адресов на одном компьютере и распределяют соединения по всем из них (для этого также могут потребоваться некоторые виртуальные локальные сетевые интерфейсы). - person Dobes Vandermeer; 24.03.2012
comment
@DobesVandermeer API не «там для этого» по той причине, которую я указал. - person user207421; 10.04.2012

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

person byte_array    schedule 19.01.2012
comment
Однако, если ваше приложение связано с сетевым вводом-выводом, как в случае с HTTP-клиентом или сервером, все заблокированные потоки не будут работать, пока ядро ​​не разбудит их, поэтому я не думаю, что они вызовут какие-либо накладные расходы на переключение контекста. Эти накладные расходы на переключение применимы только к приложениям, в которых все потоки пытаются запускаться одновременно для обработки некоторых данных. - person Dobes Vandermeer; 24.03.2012

Не существует единственного «лучшего» способа создания серверов NIO, но преобладание этого конкретного вопроса на SO предполагает, что люди думают, что есть! Ваш вопрос суммирует варианты использования, которые подходят для обоих вариантов, достаточно хорошо, чтобы помочь вам принять правильное решение.

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

person pawstrong    schedule 10.04.2012

Я бы посоветовал начать с потока на соединение и адаптироваться оттуда, если у вас возникнут проблемы.

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

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

Таким образом, я думаю, вам никогда не следует прибегать к выбору каналов NIO или асинхронному вводу-выводу (NIO 2) в 64-битных системах. Модель «поток на соединение» работает достаточно хорошо, и вы можете выполнить масштабирование до «десятков или сотен тысяч» соединений, используя более подходящую низкоуровневую технологию.

Всегда полезно избегать преждевременной оптимизации (например, написания кода NIO до того, как у вас действительно появится огромное количество подключений) и не изобретать колесо (Jetty, nginx и т. Д.), Если это возможно.

person Dobes Vandermeer    schedule 11.04.2012

Чаще всего упускается из виду, что NIO допускает нулевое копирование. Например. если вы слушаете один и тот же многоадресный трафик из нескольких процессов, используя сокеты старой школы на одном сервере, любой многоадресный пакет копируется из буфера сети / ядра в каждое прослушивающее приложение. Итак, если вы создадите GRID, например, 20 процессов, возникают проблемы с пропускной способностью памяти. С помощью nio вы можете исследовать входящий буфер, не копируя его в пространство приложения. Затем процесс копирует только интересующие его части входящего трафика.

другой пример приложения: см. http://www.ibm.com/developerworks/java/library/j-zerocopy/ для примера.

person R.Moeller    schedule 27.01.2013