Проблема с поддержкой сокетов keep-alive на домашнем http-сервере

В настоящее время я экспериментирую с созданием http-сервера. Сервер многопоточен одним потоком прослушивания с использованием select (...) и четырех рабочих потоков, управляемых пулом потоков. В настоящее время я обрабатываю запросы 14k-16k в секунду с длиной документа 70 байтов, время отклика 6-10 мс, на Core I3 330M. Но это без поддержки и любых сокетов, которые я обслуживаю. Я немедленно закрываю, когда работа выполнена.

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

Мои проблемы начались, когда я начал пытаться реализовать поддержку keep-alive. При включенной поддержке я управляю только 1.5k-2.2k запросов в секунду с 100 открытыми сокетами. Это число растет примерно до 12 тыс. С 1000 открытых сокетов. В обоих случаях время отклика составляет около 60-90 мс. Я чувствую, что это довольно странно, так как мои текущие предположения говорят о том, что запросы должны повышаться, а не вниз, а время отклика должно с надеждой идти вниз, но определенно не вверх.

Я пробовал несколько разных стратегий для определения низкой производительности:
  

  • 1. Вызовите select (...)/pselect (...) со значением тайм-аута, чтобы мы могли перестроить нашу структуру FD_SET и прослушать любые дополнительные сокеты, которые появились после того, как мы заблокировали, и обслуживать любую обнаруженную активность сокета.    (кроме низкой производительности, также проблема закрытия сокетов во время блокировки, в результате чего select (...)/pselect (...) сообщает о плохом дескрипторе файла.)  
    • 2. У вас есть один прослушивающий поток, который принимает только новые подключения и один поток keep-alive, который уведомляется через канал любых новых сокетов, которые были получены после того, как мы заблокировали и какую-либо новую активность сокета, и перестроим FD_SET.    (такая же дополнительная проблема здесь, как в "1." ).  
      • 3. выберите (...)/pselect (...) с таймаутом, когда нужно выполнить новую работу, отсоедините запись связанного списка для сокета, который имеет активность, и добавьте его обратно, когда запрос был обслужит. Ожидается, что восстановление FD_SET будет быстрее. Таким образом, мы также избегаем попытки прослушивания любых дескрипторов плохих файлов.  
        • 4. Комбинированные (2.) и (3.).
        • -. Возможно, еще несколько, но они избегают меня.

Сокеты keep-alive хранятся в простом связанном списке, методы добавления/удаления которого окружены блокировкой pthread_mutex, функция, ответственная за восстановление FD_SET, также имеет эту блокировку.

Я подозреваю, что это постоянная блокировка/разблокировка мьютекса, который является главным виновником здесь, я пытался рассказать о проблеме, но ни gprof, ни google-perftools не были очень дружественными, либо вводили экстремальную нестабильность, либо просто отказывались от собрать все данные (все это может быть я не знаю, как правильно использовать инструменты). Но удаление блокировок рискует помещать связанный список в состояние несовместимости и, возможно, сбой или поместить программу в бесконечный цикл. Я также подозревал тайм-аут select (...)/pselect (...), когда я его использовал, но я уверен, что это не проблема, так как низкая производительность поддерживается даже без нее.

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

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

Ответ 1

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

Если, однако, вы используете сокеты несколько раз от одного и того же клиента для нескольких запросов, вы потеряете накладные расходы TCP-соединения и получите такую ​​производительность.

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

http://www.techrepublic.com/article/using-the-select-and-poll-methods/1044098

На этой странице есть исправление для linux для обработки устройства опроса. Возможно, некоторое понимание того, как это работает, и вы можете использовать одну и ту же технику в своем приложении, а не полагаться на устройство, которое не может быть установлено.

Ответ 2

Попробуйте полностью избавиться от выбора. Вы можете найти какое-то уведомление о событиях на каждой популярной платформе: kqueue/kevent на freebsd(), epoll на Linux и т.д. Таким образом, вам не нужно перестраивать FD_SET и в любое время добавлять/удалять просмотренные fds.

Ответ 3

Существует много альтернатив:

  • Использовать процессы вместо потоков и передавать дескрипторы файлов через сокеты Unix.
  • Поддержание списков сокетов в потоках. Вы можете даже accept() непосредственно в рабочих потоках.
  • и т.д...

Ответ 4

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