Не могу поверить, что в самом популярном сервере поддержка пула потоков для длительных операций появилась только в 2015 году. При том, что паттерн Proactor известен науке по меньшей мере с 90-х.
Не очень понял, какая логическая связь между двумя процитированными предложениями. Мне кажется её нет, но отвечу. Не было необходимости, да и сейчас в большинстве случаев включать пул потоков нет необходимости. Для большинства типичных задач он не нужен. И даже в тех случаях, когда он нужен, есть и другие варианты решения проблемы.
Забавно, что автор винит ОС в том, что нет возможности узнать, какие данные закешированы, а какие нет.
Кешируйте сами — делов-то!
И файловую систему пишите сами, и tcp-стек, и т.д. по списку. Как только вы начинаете кэшировать данные сами, то натыкаетесь на необходимость копировать огромные объемы данных из ядра в пользовательское пространство и обратно. Такой системный вызов, как sendfile() был создан специально для того, чтобы этим не заниматься. Вы наверное не сталкивались, когда производительность начинает упираться в память и приходится делать всё возможное, чтобы уменьшать количество копирований.
Именно так работает, например, video download сервер известной социальной сети. То, что закешировано в памяти, он отдаёт сразу из потока-селектора. А за тем, чего в кеше нет, обращается асинхронно из отдельного пула. В результате один сервер отдаёт наружу до 40 Гбит/с, причём сам сервер написан даже не на C, а на «тормозной» Java.
Безусловно есть разные подходы к решению задачи. Каждый подход имеет свои плюсы и минусы. Инженеры из известной социальной сети выбрали такой подход и мы не знаем всех факторов, которые повлияли на их решение, поэтому я не возьмусь, например, давать оценки.
Информация про 40 Гбит/с особо не говорит ни о чем, ибо число сильно далеко от того, чтобы производить впечатление в 2015 году. И если уж мериться, то нужно делить этот показатель на стоимость сервера. Одно дело раздавать 40 Гбит/c с одного сервера, а другое дело раздавать треть всего интернет трафика в США и значительную долю всего мирового трафика с серверов, собранных из недорогих комплектующих. Когда количество серверов исчесляется десятками и сотнями тысяч, то их стоимость начинает иметь существенное значение. Ну и т. д. ;)
Да, я пару месяцев назад общался с Милошем. С его патчами нам будет не хватать sendfile(). В этом отношении подход Мортона чуть более универсален, хотя менее эффективен и порождает маловероятный на практике, но всё же существующий race-condition между вызовом fincore() и соответвующим read() или sendfile(). С другой стороны fincore() нам не поможет при записи. При определенных условиях write() блокируется и хорошо бы об этом узнать заранее, чтобы отправить соответвующий таск в пул.
Почитал про overlapped. Кажется это скорее про реализацию нормального ядерного AIO и отдельные потоки там не нужны. Но я могу ошибаться, поскольку плохо знаком с windows даже на уровне пользователя.
Важно понимать, что текущая реализация пулов не делает nginx многопоточным в плане обработки событий, он по-прежнему обрабатывает события на дескрипторах асинхронно и в одном потоке на рабочий процесс. А отдельные потоки из пулов используются исключительно для выгрузки долгих блокирующих операций, при этом они изолированы от основного потока через две сериализованные очереди (очередь ожидающих обработки заданий и очередь выполненных заданий).
Для интенсивного счета можно будет настроить отдельный пул потоков. NGINX позволяет создать их сколько угодно. Зачем смешивать все операции в одну кучу? У него будет своя очередь и необходимое количество тредов.
Наверное я тут перепутал с libaio. То POSIX AIO, что сейчас в glibc — это тоже обертка, но уже над тредами и довольно примитивная, чтобы её использовать для серьезных задач.
Нет. IOCP тут ортогонально. Прелесть данного механизма пулов потоков заключается в том, что он абсолютно независим от основного процесса обработки событий, не использует с ним разделяемых ресурсов, кроме двух очередей, что позволяет практически избежать локов и вмешательств в основные структуры.
Если вопрос ко мне, то не могу ничего порекомендовать, поскольку нет такого опыта, не пользовался. Вообще протокол в самом базовом виде достаточно простой и можно свое написать за день.
И мне кажется обработкой событий не fastcgi библиотека должна заниматься.
Чаще всего не используется. В Firefox, например, это нужно включать в about:config отдельно, см. network.http.pipelining.
На сервере ничего сложного в обработке таких запросов нет.
*CGI/HTTP должно хватить для большинства задач. Едва ли вы упретесь в протокол, зато получится гибко, стабильно и это будет на порядок легче поддерживать.
Планы были, но пока отсутствует понятный, формализованный, стабильный и безопасный API для сторонних модулей, любая такая документация будет неполноценной и быстро устаревать. На данный момент самая вменяемая документация — исходный код.
Собственно причем тут опенсорс. Не видел статистики, где бы утверждалось, что качество кода в закрытых проектах в среднем лучше. В данном случае у нас хотя бы есть возможность заглянуть внутрь и понять, что код говно.
Согласен. Столько далеко идущих выводов и вся аргументация строится на примере OpenSSL. Но нельзя это поделие рассматривать в качетсве примера, это просто адов ад, а не библиотека.
На всякий случай поясню, поскольку похоже есть еще люди, кто не в курсе дел.
Rambler отношения к NGINX не имеет. Игорь в Рамблере не работал программистом, а с 2011 и вовсе там не работает. NGINX это уже 4-ый год как NGINX, Inc.
Я теперь понял, что никакой сериализации сокетов больше нет, грубо говоря, epoll() и т.д. так же больше не нужны? Теперь ядро само пробуждает потоки по очереди?
Epoll никуда не делся, он всегда был отдельный в каждом рабочем процессе. Помимо принятия новых соединений, рабочие процессы делают много другой работы: читают запросы, отправляют ответы, обрабатывают таймауты, устанавливают соединения с бекендами. Рабочий процесс не может ждать на accept(), ему нужно работать с другими событиями, мониторить другие дескрипторы. Поэтому нужен механизм уведомления о событиях.
Как было:
все воркеры спят -> new socket data -> notification всех воркеров -> все воркеры вступают в борьбу за лок -> один захватывает лок и принимает коннект, остальные засыпают
Если интенсивность поступления новых соединения маленькая — то да. Для борьбы с этим эффектом как раз и существует accept_mutex, который отключает нотификацию у отдельных процессов в этом случае. Это можно видеть во втором бенчмарке, его включение снижает нагрузку на CPU.
Если нагрузка большая, как в первом бенчмарке в статье, то там всегда в общем-то есть новые соединения в очереди. И ситуаций когда рабочий процесс не получает соединения — практически не бывает. В этом случае accept_mutex скорее вредит, поэтому без него RPS получает немного выше. Тем не менее, лок в ядре никуда не девается, и когда несколько процессов одновременно зовут accept() на одном и том же сокете, то часть из них в итоге тратит на это больше времени, поскольку блокируются и вынуждены в ядре ждать своей очереди на получение лока.
Как стало:
воркеры спят в accept() -> new socket data -> ядро выбирает любой воркер, пробуждает и т.д.
См. выше. Воркерам не позволительно ждать на accept(), им нужно другие соединения обрабатывать.
Но то, что теперь будет уведомляться только один воркер, поскольку он мониторит только свои собственные дескрипторы, а не общие на все процессы — это верно. Но спать он по-прежнему будет в epoll_wait().
Тогда штука реально опасная, особенно учитывая политику распределения коннектов. Какой-нибудь залогиненный пользователь для которого страница генерируется гораздо дольше чем для анонимного всегда будет попадать на один и тот же воркер, забивая его.
Не настолько на самом деле. Псевдослучайное распределение будет раскидывать новые подключения от него случайным образом. С тем же успехом и раньше, установив keepalive соединение, можно было нагрузить запросами один рабочий процесс больше другого. Но в масштабах десятков и сотен тысяч соединений — все это довольно незначительные эффекты.
И файловую систему пишите сами, и tcp-стек, и т.д. по списку. Как только вы начинаете кэшировать данные сами, то натыкаетесь на необходимость копировать огромные объемы данных из ядра в пользовательское пространство и обратно. Такой системный вызов, как
sendfile()
был создан специально для того, чтобы этим не заниматься. Вы наверное не сталкивались, когда производительность начинает упираться в память и приходится делать всё возможное, чтобы уменьшать количество копирований.Безусловно есть разные подходы к решению задачи. Каждый подход имеет свои плюсы и минусы. Инженеры из известной социальной сети выбрали такой подход и мы не знаем всех факторов, которые повлияли на их решение, поэтому я не возьмусь, например, давать оценки.
Информация про 40 Гбит/с особо не говорит ни о чем, ибо число сильно далеко от того, чтобы производить впечатление в 2015 году. И если уж мериться, то нужно делить этот показатель на стоимость сервера. Одно дело раздавать 40 Гбит/c с одного сервера, а другое дело раздавать треть всего интернет трафика в США и значительную долю всего мирового трафика с серверов, собранных из недорогих комплектующих. Когда количество серверов исчесляется десятками и сотнями тысяч, то их стоимость начинает иметь существенное значение. Ну и т. д. ;)
sendfile()
. В этом отношении подход Мортона чуть более универсален, хотя менее эффективен и порождает маловероятный на практике, но всё же существующий race-condition между вызовомfincore()
и соответвующимread()
илиsendfile()
. С другой стороныfincore()
нам не поможет при записи. При определенных условияхwrite()
блокируется и хорошо бы об этом узнать заранее, чтобы отправить соответвующий таск в пул.Милош настроен твердо: twitter.com/mtanski/status/611932316865298433
Возможно sebres сможет ответить.
Важно понимать, что текущая реализация пулов не делает nginx многопоточным в плане обработки событий, он по-прежнему обрабатывает события на дескрипторах асинхронно и в одном потоке на рабочий процесс. А отдельные потоки из пулов используются исключительно для выгрузки долгих блокирующих операций, при этом они изолированы от основного потока через две сериализованные очереди (очередь ожидающих обработки заданий и очередь выполненных заданий).
Есть ещё выступление Валерия Холодкова на HL++ 2008 и статья по мотивам: www.grid.net.ru/nginx/nginx-modules.html и его же блог, посвященный исследованиям внутренностей nginx: www.nginxguts.com
Кроме того он реализован только под *nix.
aio_*() просто обертка над всем этим.
И мне кажется обработкой событий не fastcgi библиотека должна заниматься.
network.http.pipelining
.На сервере ничего сложного в обработке таких запросов нет.
Планы были, но пока отсутствует понятный, формализованный, стабильный и безопасный API для сторонних модулей, любая такая документация будет неполноценной и быстро устаревать. На данный момент самая вменяемая документация — исходный код.
Rambler отношения к NGINX не имеет. Игорь в Рамблере не работал программистом, а с 2011 и вовсе там не работает. NGINX это уже 4-ый год как NGINX, Inc.
Если интенсивность поступления новых соединения маленькая — то да. Для борьбы с этим эффектом как раз и существует accept_mutex, который отключает нотификацию у отдельных процессов в этом случае. Это можно видеть во втором бенчмарке, его включение снижает нагрузку на CPU.
Если нагрузка большая, как в первом бенчмарке в статье, то там всегда в общем-то есть новые соединения в очереди. И ситуаций когда рабочий процесс не получает соединения — практически не бывает. В этом случае accept_mutex скорее вредит, поэтому без него RPS получает немного выше. Тем не менее, лок в ядре никуда не девается, и когда несколько процессов одновременно зовут accept() на одном и том же сокете, то часть из них в итоге тратит на это больше времени, поскольку блокируются и вынуждены в ядре ждать своей очереди на получение лока.
См. выше. Воркерам не позволительно ждать на accept(), им нужно другие соединения обрабатывать.
Но то, что теперь будет уведомляться только один воркер, поскольку он мониторит только свои собственные дескрипторы, а не общие на все процессы — это верно. Но спать он по-прежнему будет в epoll_wait().
Не настолько на самом деле. Псевдослучайное распределение будет раскидывать новые подключения от него случайным образом. С тем же успехом и раньше, установив keepalive соединение, можно было нагрузить запросами один рабочий процесс больше другого. Но в масштабах десятков и сотен тысяч соединений — все это довольно незначительные эффекты.