Как масштабировать TCP-прослушиватель на современных многоядерных / многосетевых машинах
У меня есть демон для записи на C, который должен обрабатывать 20-150K TCP-соединений одновременно. Они - длительные связи, и редко когда-либо разрушают. Они имеют очень небольшое количество данных (редко превышающее MTU даже... это протокол стимула / ответа) при передаче в любой момент времени, но время отклика на них является критическим. Мне интересно, что текущее сообщество UNIX использует для получения большого количества сокетов, и минимизирует задержку при ответе на них. Я видел проекты, вращающиеся вокруг мультиплексирования соединений с рабочими пулами форка, потоками (для каждого соединения), пулами потоков статического размера. Какие-либо предложения?
6 ответов
Самое простое предложение - использовать libevent, это позволяет легко написать простой неблокирующий однопоточный сервер, который бы соответствовал вашим требованиям.
если обработка каждого ответа занимает некоторое время или если он использует какой-то блокирующий API (как почти все из БД), то вам понадобится некоторая многопоточность.
Один из ответов - рабочие потоки, где вы создаете набор потоков, каждый из которых прослушивает определенную очередь для работы. это могут быть отдельные процессы, а не потоки, если хотите. Основным отличием будет механизм связи, чтобы сказать рабочим, что делать.
Другой способ состоит в том, чтобы использовать несколько потоков и дать каждому из них часть этих 150К соединений. каждый из них будет иметь свой собственный цикл процесса и работать в основном как однопоточный сервер, за исключением порта прослушивания, который будет обрабатываться одним потоком. Это помогает распределить нагрузку между ядрами, но если вы используете блокирующий ресурс, он заблокирует все соединения, обрабатываемые этим конкретным потоком.
libevent позволяет использовать второй способ, если вы осторожны; но есть и альтернатива: libev. он не так хорошо известен как libevent, но он специально поддерживает многоконтурную схему.
Если производительность критична, то вам действительно нужно использовать многопоточное решение для обработки событий - т. Е. Пул рабочих потоков для обработки ваших соединений. К сожалению, для этого нет библиотеки абстракций, которая бы работала на большинстве платформ Unix (обратите внимание, что libevent является однопоточным, как и большинство этих библиотек цикла обработки событий), поэтому вам придется выполнять грязную работу самостоятельно.
В Linux это означает использование epoll-триггера, запускаемого по фронту, с пулом рабочих потоков (в Windows были бы порты завершения ввода-вывода, которые также отлично работают в многопоточной среде - я не уверен насчет других Unix-систем).
Кстати, я проделал некоторую работу, пытаясь абстрагировать краевой триггер на портах завершения ввода-вывода Linux и Windows на http://nginetd.cmeerw.org/ (он находится в стадии разработки, но может дать некоторые идеи).
Если у вас есть доступ к конфигурации системы , не переусердствуйте и настройте некоторые iptables/pf/etc для балансировки нагрузки между n экземплярами (процессами) демона, поскольку это будет работать из коробки. В зависимости от того, насколько заблокирован характер демона, n должно быть из числа ядер в системе или в несколько раз больше. Этот подход выглядит грубым, но он может обрабатывать сломанные демоны и даже перезапускать их при необходимости. Также миграция была бы гладкой, так как вы могли бы начать переадресацию новых соединений на другой набор процессов (например, новый выпуск или миграцию на новый ящик) вместо прерываний обслуживания. Вдобавок к этому вы получаете несколько функций, таких как сходство с исходным кодом, которые могут существенно помочь в кэшировании и разрешении проблемных сессий.
Если у вас нет доступа к системе (или ops не может быть обеспокоен), вы можете использовать демон балансировки нагрузки (есть много открытых) вместо iptables/pf/etc и также использовать n сервисных демонов, как описано выше.
Также этот подход помогает в разделении привилегий портов. Если внешнему сервису требуется обслуживание на низком порту (<1024), вам нужен только балансировщик нагрузки, на котором выполняется привилегированное / или admin / root, или ядро.)
В прошлом я написал несколько балансировщиков IP-адресов, и они могут быть очень подвержены ошибкам в работе. Вы не хотите поддерживать и отлаживать это. Кроме того, операции и управление будут склонны угадывать ваш код больше, чем внешний код.
Я думаю, что ответ Хавьера имеет смысл. если вы хотите проверить теорию, то посмотрите проект javascript узла.
Узел основан на движке Google v8, который компилирует javascript в машинный код и работает так же быстро, как и c, для определенных задач. Он также основан на libev и разработан для того, чтобы быть полностью неблокирующим, то есть вам не нужно беспокоиться о переключении контекста между потоками (все выполняется в одном цикле событий). Это очень похоже на эрланга в этом отношении.
Написание высокопроизводительных серверов на javascript теперь очень легко с помощью узла. Вы также можете, приложив немного усилий, написать свой собственный код на c и создать привязки для узла, который будет вызывать его для выполнения фактической обработки (посмотрите на исходный код узла, чтобы узнать, как это сделать - документация немного схематична в момент). в качестве более уродливой альтернативы вы можете создать свой собственный код c как приложение и использовать stdin/stdout для связи с ним.
Я сам протестировал узел с более чем 150 тыс. Соединений без каких-либо проблем (конечно, вам понадобится серьезное оборудование, если все эти соединения будут обмениваться данными одновременно). TCP-соединение в node.js в среднем использует только 2-3 КБ памяти, поэтому теоретически вы можете обрабатывать 350-500 КБ на 1 ГБ ОЗУ.
Примечание. Node.js в настоящее время не поддерживается в Windows, но это только на ранней стадии разработки, и я предполагаю, что он будет перенесен на каком-то этапе.
Примечание 2 - вы должны убедиться, что код, который вы вызываете с узла, не блокирует
Для улучшения производительности select(2) было разработано несколько систем: kqueue, epoll и /dev/poll
, Во всех этих системах вы можете иметь пул рабочих потоков, ожидающих выполнения задач; Вы не будете вынуждены настраивать все файловые дескрипторы снова и снова, когда закончите с одним из них.
Вы должны начать с нуля? Вы могли бы использовать что-то вроде gearman.