Pull to refresh

Comments 123

UFO just landed and posted this here
Понятно, что статья не идеальная, но зачем автору этого коммента поставили столько минусов?
Обычное дело для хабростада.

От статьи хочется чуть большего, чем «на схеме хорошо видно». Хотябы один абзац почему именно схема такая, а?
Нельзя требовать от автора очень полного и подробного изложения. Не забывайте, что написал эту статью человек абсолютно бесплатно. Автор понял некоторые очень важные вещи, спасибо ему за это. Кто хочет подробности, открывайте google и ищите.
Думается мне, что минусы как раз за то, что в статье разжевано не все. Статья посвящена только вопросу асинхронности, но это не единственное отличие node.js от PHP. Множество других отличий скрыто в особенностях JavaScript, на котором написан фреймворк. Так что утверждать, что в статье «разжевано все» о различиях node.js и PHP, мягко говоря, легкомысленно.

Кстати, заметьте мы сравниваем фреймворк с языком программирования, что вообще-то не совсем корректно ;)
Разумеется вы правы, я только против того, чтобы слишком часто высказывать нетерпимость к пишущим. Читайте мой коммент выше.
node.js не совсем фреймворк (хотя спорно). И написан он вовсе не на Javascript.
UFO just landed and posted this here
Документация на сайте сейчас устаревшая. В данный момент потихоньку переводим 0.6 ветку, так что читайте сразу с github'a.
UFO just landed and posted this here
Примеры, тесты, графики? Что за БД такая однопотоковая?
БД здесь совершенно не причем. О них вообще речи нет.
Говориться о том что делая запрос в пхп, процесс блокируется до получения ответа от БД.
Один процесс/поток (при thread safety) для себя может и блокировать, но не для остальных. Если БД (sql kind) по какой-то причине тормозит (долгая вставка/удаление), то это влияет на всё, но не при SELECT запросах. PHP синхронный для одного потока и асинхронный в многопоточном режиме.
Если честно, то я так и не понял, что с чем конкретно вы сравниваете.
Видимо имеется в виду, что когда пул процессов/потоков заполнен, то даже легкий запрос будет ждать окончания хотя бы одного тяжелого в очереди веб-сервера (или вообще отбросится). Может и у NodeJS есть подобные ограничения, но из его поверхностных описаний (чуть менее поверхностных чем это) я понял, что только объёмом доступной памяти ограничен. Причём памяти требуется куда меньше, чем на новый процесс. Тяжелые ждут своих многоэтажных SELECTов от БД, а лёгкие дают в это время пачками почти статические ответы (валидируя, например, формы и выдавая ошибки юзерам до обращения к СУБД).
Не становится он асинхронным в многопоточныом режиме. Он становиться «параллельным», а «асинхронным» выглядит только до тех пор, пока количество тяжёлых запросов меньше числа потоков.
для тяжелых запросов php умеет делать асинхронные запросы к БД (по крайней мере MySQL)
Особо меня порадовало, как человек делал что-то типа такого (в примере — псевдокод) на Node и аргументировал: «у меня так же в php — и всё прекрасно работает!»
http.createServer(function (request, response) {

   RequestSingleton.set(request);
  ResponseSingleton.set(response);
  ApplicationSingleton.run();

}).listen(1337, '127.0.0.1');

PHP + libevent = отличная неблокирующая архитектура.
Остается проблема с сильным потреблением памяти, но утечек опять же нет если не допускать самому, так что терпимо. А если этого все же не хватает, то тут уже и node вряд ли кардинально изменит картину.
Если из
$app = new Application();

$request = $app->createRequestFromWeb();
$response = $app->process($request);
$app->sendResponse($response);

сделаю что-то вроде
var app = App();
http.createServer(function (request, response) {
  var req = app.createRequest(request);
  var resp = app.process(request);
  response = app.createHttpResponse(resp);
}).listen(1337, '127.0.0.1');

будет работать? Возможно перемудрил с реквестами и респонсами, но это на случай если их стандартной функциональности мне не хватит, если она вообще есть, а не массивы/структуры это.
а лет 10 назад было так:
$app = init();

$request = create_from_web($app);
$response = process($app, $request);
send($app, $response);

Просто как-то со времён BASIC и FORTRAN (то есть лет 20 назад) избегаю неявной связанности и глобальных переменных/состояний — хватит, намучался.

Если внутри app.createRequest()/app.process() есть асинхронные вызовы (а они практически всегда есть), то не будет работать.
createRequest в PHP проге это простой сеттер по сути, присваивает поля всякие $_GET, $_POST и т. п. для Request, value-object в общем, ни с чем внешним не взаимодействует.

ну а в process вроде очевидно что будет что-то вроде
var result;
db_conn.query('SELECT * FROM users WHERE referer_id = ?', app.session.id, function(result) {
  if (!result) {
    resp = NotFoundResponse(); 
  } else {
  db_conn.fetch(result), function(record) {
     // тут больша-большая лесенка, собирающая ответ
  }
  }
})

Насчёт fetch просто пример, наверное рекурсию надо мутить, чтобы получить полный список записей.
Не, не будет работать.
Ибо когда первый «поток» (Execution Context, конечно же) будет висеть и ждать ответа от базы, придёт новый запрос. И выполнит app.XXX со своими данными.
Так как app у вас объект привязанный к глобальному контексту, то внутри анонимной функции он будет одинаков для всех запросов, ну и собственно когда первый поток всё же получит ответ и продолжит выполнение вызовом createHttpResponse(), app будет содержать данные второго «потока», которые он инициализировал до того как самому встать на ожидание ответа от БД.
Единственный вариант корректной работы — это если App() содержит только методы, но не внутреннее состояние, ну или полностью синхронный.
Ах да, по mysql — result это массив записей, по крайней мере в том драйвере что я использовал.
Внутреннее состояние он содержит и оно даже меняется, но оно глобально для приложения (конфиги, коннект с БД, кеши, роуты и прочие метаданные). Всё что относится к конкретному запросу берётся из request и трансформируется в response, максимум — передаётся параметрами методов. Единственное, что после ваших комментов смущает, что Request создаётся внутри app.procсess, но просто локальная переменная, не свойство объекта app. Вроде при каждом вызове он свой будет в «потоке» тогда.
Из вашего изложния выходит, что если один проесс ПХП сможет обрабатывать несколько запросов одновременно, то это получится nodeJS. Это так? Больше принципиальных различий нет?
Из пхп тоже можно делать несколько одновременных запросов к БД не дожидаясь результата от предыдущего.
Из статьи это не следует…
Особенно статья удивляет, если принять во внимание комент romy4.
Нет, нужно ещё чтобы эти запросы он обрабатывал через колбэк в рамках одного глобального состояния, независящего от запроса и ответа. Если каким-то макаром PHP научится выполнять новый запрос пока предыдущий ждёт ответа от mysql_query, то это будет ещё не nodePHP. Как я понял.
Ну это можно организовать. Другой вопрос, что пока что такого готового решения нет.
Кстати phpDaemon как раз умеет обрабатывать в одном процессе несколько запросов. Один чего-то ждет — обрабатывается другой.
Но для того чтобы это поддерживалось все приложение должно быть спроектировано с учетом таких фич.
Только хотел написать, что phpDaemon как раз служит аналогом NodeJS. Наверное. Сам я его асинхронные фишки не пробовал, но даже вынесении инициализации (чтение конфигов, подключение к БД и т. п.) за рамки цикла обработки запросов почти на пустом месте даёт кратное увеличение скорости приложения, особенно после «разогрева» autoload. Но да, даже в этом случае от многих вредных привычек, основанных на том, что перед приёмкой запроса всё чисто, а после отдачи ответа всё почистится само, приходится избавляться (Многие кричат об утечках памяти самого PHP, но я замечал только у себя.) Для одновременной обработки придётся избавляться ещё от большего. Например от изменения глобальных переменных где-то кроме инициализации, если бизнес-логика этого напрямую не требует (глобальные для приложения счётчики).
Численные операции и взаимодействие с файлами однозначно не являются достоинствами NodeJS на данный момент, но веб-приложения основанные на websockets по идее будут гораздо более быстрыми чем у PHP
ОоО… Неужели V8 уступает в скорости PHP в численных и файловых операциях? Никто не мешает первые вынести в Web Workers, а вторые и так изначально асинхронны.

Но вообще Node.JS идеален как агрегатор.
V8 вообще-то рвёт PHP в числовых операциях как тузик грелку, а файловые и так и так ведь всё равно на Си написаны.
Сейчас разрабатываем проект на Nginx (для балансировки нагрузки) + Node.JS + MySQL memory.
Делаяли стресс тест, могу смело заявить что в такой связке, Node.JS спокойно может обрабатывать ~8000 запросов в секунду. PHP + MySQL врятле выдаст вам больше 500 запросов в секунду при хорошо затюнненом сервере.

Без Nginx результаты достигают 15 000 запросов в секунду, но из за того что Node сам по себе не имеет балансировать нагрузку, у него есть свойство иногда падать.
Без грамотного использования событий типа drain лучше использовать Node строго за nginx или HA Proxy.
Не подскажите где можно более подробно об этом почитать?
Это касается проблемы «быстрые клиенты vs медленные клиенты».

Когда клиент не может получать в данный момент данные, WriteableStream.write возвращает false, и событие drain возникает, когда клиент опять готов принимать данные.

Обычно проще поставить nginx впереди, чтобы не париться об этом, ибо он берет отдачу контента медленным клиентам на себя.
Может быть PHP и слабее Node.JS в этом сравнении, но 500 запросов в секунду это вообще смешно. Видно плохо тюнили. На виртуалке безо всякого тюнинга тысячу запросов в секунду спокойно выдает, не напрягаясь особо.
Честно скажу, лично PHP я не тестировал так что извиняйте за не очень точные цифры.
Цифры о PHP мне предоставлял несколько иной источник.
Это смотря, что за код ) Отнюдь не типичный типа поднять сессию, прочитать-записать БД и т.п.
С нормально спроектированным и написанным приложением все будет нормально даже не на типичной задаче. Я не говорю конечно про отдельные случаи, когда на странице особо тяжелые вычисления, дергается несколько апи на бэкенде и т.п.
уже умеет сам по себе, встроенное расширение cluster
PHP+Mysql? Не PHP+Mysql memory?
Да, простите.
PHP + MySQL memory.
Но опять же, я лично PHP не тестировал а учавствовал в тесте непосредственно Node.JS, так что вторая цифра может быть не точной как выше уже отметили.
UFO just landed and posted this here
Из объяснения я понял, что обращение к БД — это операция приводящая к блокировке, но только в PHP, а не в Node.js.
Потом вы пишете:
«Помните что вы работает в асинхронной среде, не используйте операции приводящие к блокировке, этим вы убиваете идею nodejs. „
Так как определить, если какая-то операция в node.js будет блокирующей, а какая — не будет? Примеры?
И самое главное, я так и не увидел объяснения почему например операция обращения к БД в PHP — блокирующая, а в node.js — нет?
Практически все API без постфикса Sync что в Node, что в расширениях (модулях), являются неблокирующими.

Наиболее известное исключение — метод require.
Блокирующая:

var data = fs.readFileSync('file.txt', 'utf-8')
do_something_with(data)

Не блокирующая:

fs.readFile('file.txt', 'utf-8', function(err, data) {
do_something_with(data)
})
Большинство операций в PHP — блокирующие. Для использования неблокирующих операций либо есть специальные фичи (например в mysqli), либо это все можно сделать самому.
В Node.JS же как бы наоборот — все строится на неблокирующей архитектуре, но можно глупо применить какую нибудь блокирующую операцию и сломать этим весь профит.
Грубо — если операция требует колбэка, которому передаст результат, а не возвращает результат сама, то она неблокирующая.

Потому что изначальная идеология другая. Один запрос — один процесс. CGI идеология. PHP язык для работы в комплексе с веб-сервером, дополнение для него, а не язык для разработки серверов. Можно сказать, что неблокирующие по своей природе функции разработчики PHP обернули в блокирующие (этот процесс) обёртки. Или использовали такие обёртки уровня ОС. Так писать удобнее и быстрее. И читать.
ИМХО, ерунда какая-то.

Я могу представить, что node.js потребляет меньше памяти, что он «как-то лучше организован в потоках», но, простите, как автор считает если Client (browser) подключается к Server (web-server) по HTTP, то сетевое соединение (TCP) оно продолжает держаться на «долгих запросах, использующих БД» или нет?

ИМХО отказом новых коннектов занимается именно веб-сервер, который принимает входящие запросы. Их предел установлен на уровне apache/nginx/lighttpd/etc. Что веб-сервер делает дальше с этим запросом (передает CGI-скрипту, запускает дочерний процесс/тред) никого не волнует.

Если автору на его nginx одновременно придет 16000 «долгих» запросов, для которых Node.Js будет ждать ответа от БД. Node.JS все равно «подвиснет» с 15000 и заблокирует все остальные 1000 «легких» (в очереди socket'а), если они вообще не будут отправлены «домой» (connection refused).

Более интересно понять почему Node.Js такой легкий и быстрый… Возможно потому что в нем почти ничего нет? (никаких библиотек)

Это как написать на С web-сервер, который на любой запрос будет отвечать «Hello, world!» и закрывать соединение. Таких и 100 000 в секунду можно обработать (если канал позволит)…
>Более интересно понять почему Node.Js такой легкий и быстрый… Возможно потому что в нем почти ничего нет? (никаких библиотек)

Это как написать на С web-сервер, который на любой запрос будет отвечать «Hello, world!» и закрывать соединение. Таких и 100 000 в секунду можно обработать (если канал позволит)…

Очередное незнание матчасти. Скорость работы инвариантна к количеству библиотек.
С одной стороны да, с другой — нет.

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

Я лишь хотел намекнуть автору, что архитектура систем, приведенная на картинках, для обоих вариантов — одна и та же.

Node.JS не блокирует HTTP-запросы не потому, что у него «архитектура особая», а потому что он позволяет параллельно поднимать больше обслуживающих процессов/тредов.

Интерес же представляет «магия», которая происходит внутри Node.JS, которая позволяет экономично расходовать ресурсы ОС/сервера по сравнению с CGI/PHP.

И да, для того, чтобы обслуживать массу запросов, вовсе нет необходимости поднимать кучу процессов/тредов — посмотрите на тот же Nginx — если не изменяет память: 4 процесса по 1000 обслуживаемых соединений.
Это вы вводите в заблуждение, nodejs не блокирует потому что не блокирует, эта идея была заложена в ее основу изначально, и все модули пишутся в этом стиле. Она не поднимает ни каких дополнительных потоков/процессов.

Вы привели пример с nginx, вот именно те же идеи что заложены в nginx и заложены в nodejs, именно поэтому один процесс nodejs может обрабатывать тысячи запросов.

Вся суть этой статьи может быть высказана одним предложением из описания nodejs: «Node.js uses an event-driven, non-blocking I/O model.»
У него именно архитектура особая. Не поднимается на каждый запрос весь стэк, только при старте. «Магия» заключается в том, что тяжелые (но не блокирующие) запросы не мешают выполнятся лёгким, пока тяжелые ждут ответа от внешних систем — ФС, СУБД и т. п. Они передали SQL запрос какой-нибудь библиотечной mysql_query и не дожидаясь ответа (вернее она ответила — ок, приняла) вернули управление серверу, а уж сервер смотрит очередь готовых ответов и очередь слушающего сокета и как-то решает какой следующий колбэк вызвать. «Магия» в том, что в один момент времени обрабатывается только один запрос, многозадачность не вытесняющая (средствами ОС), а совместная (обработка запроса освобождает поток или явно, возвращая управления, либо неявно, вызывая неблокирующую функцию). Обратная сторона — один тяжелый блокирующий запрос ( while(1) ) заблокирует вcё.

Это так, в силу моего понимания.
Разница PHP (CGI, SAPI, FastCGI) и NodeJS, как я понимаю, как между Apache и nginx.

NodeJS сам является веб-сервером (вернее инструментов для их создания). Идеологически перед ним ставить nginx для проксирования просто незачем. Никакого профита это толком не даст (не считая, что nginx написан на C, а сервер на NodeJS — JS и, наверное, для статики всё же стоит поставить nginx). Он сам (без nginx) обслужит 1000 лёгких запросов не дожидаясь окончания выполнения 15000 тяжелых. Но в памяти будет висеть не 15000 процессов/потоков, ожидающих ответ от СУБД, как в случае с Apache+mod_php, а один, сетевые соединения держаться будут, но вот очередь сокета будет пуста. Придёт лёгкий запрос — он обслужится «вне очереди», тяжёлый будет ждать когда ему ответит СУБД. Лишняя память будет тратиться только для структур данных запроса и ответа, для самого сервера/приложения она выделится только раз.

А даже если поставить nginx, то все соотвествующие запросы он будет передавать NodeJS, примерно как в случае с php-fpm (или вернее Apache+mod_php), но обрабатывать их будет один процесс, а не форки.
nginx нужно ставить для статики, nodejs не справится с этой задачей так же эффективно. Плюс, если у вас один голый nodejs-сервер, то вы теряете виртуальные хосты, сертификаты, авторизацию и другие фичи «настоящих» веб-серверов, вроде nginx и apache. То же самое с tornadoweb, написанном на питоне — не смотря на асинхронное исполнение, он не может обеспечить работу в полной мере. Поэтому запускают несколько экземпляров tornado и распределяют по ним запросы через nginx.
Разве я не смогу анализировать заголовок Host запроса, чтобы сделать свои виртуальные хосты а-ля <user_name>.example.com? Тоже и с остальными фичами «настоящих» веб-серверов. Или что-то, они они не умеют, например websoсkets? Насколько это эффективно будет и стоит ли писать на JS то, что уже написано на C, отдельный вопрос. Хотя бы в целях самообразования, чтоб понять как под NodeJS писать.

А несколько экземпляров зачем? Комбинировать вытесняющую и совместную многозадачность, на случай если один процесс всё же задумается (будет что-то считать)?
Написать можно всё, что угодно, в т.ч. проверку клиентских сертификатов, разделение по виртальным хостам, но это будет колоссальная потеря времени и ресурсов.

Tornado работает в одном процессе, поэтому, пока один запрос не обработан, не поступят на обработку новые. При всей асинхронности сервер может заткнуться на извлечении урла, записи в какой-то ресурс и тд. Здесь на выручку придет второй экземпляр сервера.
Асинхронные приложения намного сложнее писать и дебажить. Тут становится вопрос рентабельности — проще купить арендовать сервер за 3тр/м чем программиста за 70тр/м (хм, и того, если подумать, 1 программист = 20 машин)
Дебажить положим чуть сложнее, а в чем проблема писать их? Сложность ничуть не выше, просто подход другой. А иной раз как раз асинхронный вариант проще дается.
UFO just landed and posted this here
Всё же сложнее. Там где обходились одной функцией — нужно две (причём вложенных обычно). Там где обходились присваиванием возвращенного этой функцией значения нужно устраивать замыкание. А уж если хочешь получить преимущества параллельного выполнения внешних и внутренних вычислений, или двух внешних, то вообще изголяться приходится. Про опасность положить всё приложение, а не только текущий запрос, бесконечным блокирующим циклом я вообще молчу.
Наверно все пропустили самое начала статьи. Где я оговорился, что это для «новичков». Для новичков не nodejs а вообще в веб. Большинство новичков начинают изучать именно с пхп и со всем известного LAMP (в лучшем случае) или денвер. Они только начинают разбираться что есть что. Попробовав немного «пхп» они начинают пробовать nodejs. Вот именно для таких людей я это и писал. Я не старался показать что лучше а что хуже, я лишь пытался объяснить то, что самому когда то было не понятно.
UFO just landed and posted this here
Простите меня, я новичок на хабре, если задеваю чувства людей которые работают с php, написанием названия русскими буквами. Еще раз простите, не знал, исправлюсь. Впредь буду писать все одинаково.
UFO just landed and posted this here
Разве речь в статье о скорости?

Да можно сделать асинхроннысть на php и если мне не изменяет память, это уже сделано в phpDaemon.

Я писал для новичков. Целью было объяснить на простом примере разницу в технологиях, показать в простой форме, без завала страшных и не понятных слов.
Нда… набежали phpшники и закидали всех кирпичами, как мило.
Никого я не закидывал :( Наоборот пытался объяснить коллегам плюсы асинхронного неблокирующего подхода (минусы почти не упоминал).
Почему в вашем комментарий PHP это пхп, а nodejs — nodejs? Ну слава Богу хоть не пэхапе :)
правильно говорить «похапэ» )
Большинство задач, которые решаются на PHP, требуют синхронного исполнения кода. К примеру, обращение к ДБ — построение страницы.
Совсем нет.
Например у нас идет несколько выборок из БД. Мы можем отправить их все сразу и обрабатывать по мере поступления результатов. После завершения последнего строим страницу. Просто это непривычно для PHP и нет действительно хороших фреймворков под такую архитектуру, где такое было бы удобно применять.
Это уже будет эффективнее, чем последовательное выполнение каждого запроса отдельно. mysqli причем поддерживают такую возможность из коробки для native driver реализации.
Пока мегазапросом вытягиваются 1000+ древовидных комментов к посту, не проще ли занять сервер чем-то вроде шаблонизации остальной части страницы, от этого мегазапроса не зависящей? Или просто отправить второй запрос к той же БД на обновление статистики?
есть же всегда кейз что, что то при вытягивании комментов происходит не так, то ли комменты не те, то ли ошибка и т.д. а мы уже сервер напрягли и он шаблон компилил все это время, зачем ему это?
Я отдаю себе отчет что асинхронность это хорошо, но ведь т сложно потому что порой мы не можем гадать на перед, мало того, человек сам по своей природе «синхронный», «однозадачный» по этому и трудно многи думать в асинхронном стиле.
Вероятно имело ввиду собрать шаблон для другого клиента.
ну так это другое дело :) т.е. по сути в любом случае самое основное преимущество ноды в том что работа над одним «клиентом» не заставляет ждать всех других клиентов, так? а если рассмотреть в рамках одного клиента то далеко не всегда есть возможность асинхронно сделать то и то для одного и того же «клиента», т.к. в любом случае надо будеть ждать ответ ото всех своих асинхронных вызовов что бему страницу показать какую-то.
Но у меня вопрос ниже, почему ПХП заблокирует в таком же кейсе работу других своих процессов?
Нет, я как раз работу для одного клиента имел в виду. Да, вероятность, что что-то пойдёт не так есть при «параллельном» выполнении и результаты других потоков будет не нужны и их придётся отбросить (а то и дожидаться конца выполнения), но чаще всё же выполнение нормальное.
Потому, что php (phpDaemon не считается) работает практически в CGI режиме. На каждый запрос скрипт вновь собирается и исполняется (оп код кэшируется, но суть та же). И вот воркеров у php-fpm много не наплодить. Если все воркеры (а их не много) будет заняты тяжелыми запросами (причем они тупо будут ждать базу в большинстве случаев), то даже тот клиент который просто хочет открыть главную будет ждать свободного воркера. При этом раз в n запросов php-fpm должен отреспаунится, потому, что большинство скриптов не умеют работать в fcgi режиме и текут.
Я понимаю что nodeJS асинхронный, и что в случае с тяжелым запросом он не заблокирует другие запросы к серверу, а значит пока один клиент ждет другие (если не делают ничего такого же медленного) получают данные достаточно быстро, но вот вопрос:
почему в php если один запрос медленный, то другие (боле быстрые) не проходят?
Потому что на каждый запрос в PHP выделяется память и есть определенный предел на количество одновременно живущих запросов. А в Node.JS другая архитектура. Там коннект есть, но на него выделяется память, если он что-то делает.
Ведь не зависимо от подохода и архитектуры нету ничего бесконечного, и если скажем у нас есть тяжелый запрос и аналогичных ему пришло 100тысяч то все равно может не хватить ресурсов. Профит как я вижу только в том что другие клиенты до поры до времени не будут чувствовать на себе эту ситуацию… так?
Разумеется, Node.JS не панацея. Рано или поздно и она не сможет обрабатывать запросы.
Пока будет хватать ресурсов на обработку запросов, они будут обрабатываться условно параллельно и там, и там. На PHP даже «параллельней» (вытесняющая многозадачность даст работать каждому обработчику в любых разумных условиях, и один «зависший» не заблокирует все остальные, хотя и затормозит их). Но для типичных приложений требуется куда больше ресурсов (прежде всего оперативки) для обработки запроса на PHP, чем на NodeJS. За счёт этого он может обработать больше запросов, если они написаны асинхронно. тяжелый синхронный наоборот повесит все.
До определенного предела вполне работает (особенно если БД на другом сервере/ядре и её нагрузка на скорость у PHP не влияет). Но по исчерпанию пула/памяти/других лимитов просто некому будет запрос обрабатывать. В лучшем случае он будет стоять в очереди.

UFO just landed and posted this here
Похоже nodejs активно забирает программистов PHP которые обожают книги типа "*** За 24 Часа!" или "*** Глазами хакера"
Так-то это совсем не хорошо, боюсь как бы нода не стало новым пхп (в качестве нарицательного). Хотя с другой стороны тут попроще выстрелить в себе в ногу, так что как набигут, так и отбегут, остануться лишь те, кто умеет пользоваться инструментом и не подвержен религиозному программированию.
Раз статья для начинающих, то надо было раскрыть аспект работы node.js на виртуальном хостинге за 10р/месяц (-:
Как же вы достали php-теоретики со своими доказательствами, «phpDaemon как раз служит аналогом NodeJS» чтоу? Php не может работать асинхронно вообще! У него это в ядре не предусмотрено. Чтобы понять что такое асинхронность вы должны для начала понять принцип работы той же libevent. Как вы умудряетесь вешать libevent с callbacks на свой расфоркиватель и говорить при этом что у вас асинхронная работа? Или может вы не видите разницу между fork и callback? «Я писал для новичков» пишет автор статьи, коим сам и является раз умудрился создать такую статью.
А что в этом плохого?
Php не может работать асинхронно вообще! У него это в ядре не предусмотрено. Чтобы понять что такое асинхронность вы должны для начала понять принцип работы той же libevent.

Ололо, не несите чушь. Ничего не мешает php обрабатывать множество запросов в одном потоке с помощью libevent и поддержка ядра тут ни при чем.
Ну почему практически во всех темах про event-driven сервера и фреймворки набегает куча комментаторов, неотличающих тредовую и событийную модели, и начинает нести какую-то ахинею?
Потому что тема event-driven не поднимается в темах, внутренняя архитектура не описывается.
Статья ни о чём. Сравнивать язык и фреймворк не просто некорректно, но и в последнее время стало уже моветоном. То RoR с PHP сравнивают (а не с ZF или Symfony, например), то теперь вот node.js с PHP.
Если уж сравнивать, то брать либо голый Javascript и голый PHP, либо nodejs и phpDaemon (или другой демонический фреймворк на PHP).

Статью в топку.
В PHP всё же есть признаки веб-фреймворка («ручками» не надо настраивать взаимодействие с веб-сервером в подавляющем большинстве случаев, за передачу GPCE и т. п. отвечает ядро, очередями запросов и т. д. рулит оно же в компании с веб-сервером). И если уж сравнивать PHP с JS, Ruby, Python и т. д. корректно, то сравнивать только в CLI, максимум CGI режиме. Но редко используют так все 4 языка, хоть какая-то функциональность взаимодействия с вебом (слушание сокетов, преобразование HTTP потока в какие сущности запроса, ожидаемый способ ответа) берётся готовой. Сравнение PHP с RoR конечно некорректно, а вот Apache+mod_php/ php-fpm с Rack, WSGI или NodeJS, имхо, вполне корректно в типичных сценариях использования в вебе.
У «классического» PHP (даже плюс nginx и плюс php-fpm) совершенно иная концепция, по сравнению с NodeJS. Поэтому тут сравнивать что-либо совершенно неправильно.
Так и сравниваются концепции. Вообще говоря, NodeJS даже более низкоуровневый чем «классический» PHP — приложение (наш код) само является сервером и запускается пользователем. PHP же в этом отношении классический фреймворк — наш код вызывается ядром. Вот такая инверсия управления.

По сути NodeJS даже не фреймворк, а интерпретатор JS (вернее консольная обёртка для V8) и набор библиотек. И, наверное, можно даже реализовать концепцию PHP на нём (если есть биндинги к fork и т. п.) без всякой асинхронности в собственно обработчиках — слушаем сокет, приняли запрос, форкнули обработчик, слушаем дальше…
UFO just landed and posted this here
Только частично — для статики. Если все воркеры php будут ждать ответа от СУБД, то nginx не сможет обработать следующий запрос к php.
UFO just landed and posted this here
Нет, с синхронными блокирующими вызовами. Пока воркер PHP ждёт ответа от блокирующей функции он другой запрос обработать не может. В NodeJS, в приложении написанном по его «гайдлайнам», он её просто вызывает и готов обрабатывать следующий запрос. Когда через колбэк она «вернёт» результат, он продолжит обработку первого запроса с этим результатом. Если нужно. А ответ пользователю может вернуть сразу после вызова, например 202 или 205. В PHP такое реализуется не тривиально.
UFO just landed and posted this here
Просто потому что Apache не нужен. И NodeJS ничего не форкает. Модель вроде: куча запросов — один процесс. Контекст запроса хранится где-то там (в стэке?), а для его восстановления не нужно вызывать «дорогие» функции переключения задачи (пускай даже ОС сама это делает, это ещё хуже). Вся модель строится на том, что один процесс всегда занят реальным делом или ждёт возможности им заняться. А не как в PHP: куча процессов ждут пока им ответят, ОС их по очереди дергает, выделяя им CPU чтобы они подождали.

Главная фишка, имхо, в том что на один процесс JS на 100500 HTTP запросов нужно качественно меньше памяти чем на 100500 процессов PHP на 100500 HTTP запросов.

Хотя всё зависит от приложения и лимитов — если каждый запрос требует взять в память гигабайт данных, а у вас её всего полгига, то, думаю, вам надо думать над чем-то другим прежде смены платформы.
UFO just landed and posted this here
Вы описали сейчас как работает классический PHP (и не только). Приходит 10 тяжелых запросов, главный процесс форкает для каждого обработчик и ждёт ответа или нового запроса (если есть возможность форкать ещё). В свою очередь каждый обработчик доходит до вызова mysql_query, вызывает и ждёт ответа от MySQL. В памяти у нас 11 процессов полноценных висят, 10 из которых (практически одинаковых) только ждут ответа от MySQL, а он обрабатывает их запросы в общем случае последовательно, в порядке поступления. И один, ждущий поступления нового запроса. Если ответы от MySQL приходят одновременно, то ОС начинает делить CPU между этими десятью процессами и пользователи получают ответ тоже одновременно.

В случае же асинхронного событийно-управляемого сервера у нас только один процесс, который знает на какое событие какой обработчик внутри себя запустить. В начале он знает только о событии «запрос», приходит запрос и он вызывает (внутри себя) обработчик запроса, тот работает, доходит до запроса к MySQL, формирует обработчик события «mysql вернул ответ на такой-то запрос», вызывает сам запрос и возвращает управление в цикл обработки событий. И так ещё 9 раз. В итоге в памяти у нас только один полноценный процесс, ждущий событий — или события «новый запрос», или события «пришёл ответ от MySQL». Структура данных ожидаемого события куда меньше полноценного процесса. Если ответы от MySQL пришли одновременно, то ответы отдаются по очереди.

Может с цифрами проще будет. Пускай подготовка SQL запроса занимает 2 мс, его выполнение (на другом сервере, практически параллельное, больше на пинги уходит) 10 мс, шаблонизация результата 4 мс. Приходят 5 запросов одновременно:

«Классический» сервер:
+0 мс: все 5 запросов начинают выполняться параллельно
+10 мс: уходит 5 запросов к СУБД (сервер простаивает)
+20 мс: приходит 5 ответов СУБД, начинает выполняться шаблонизация параллельно
+40 мс: уходит 5 ответов на запросы

Все времена (минимальное, среднее и максимальное) 40 мс. 25% времени сервер простаивал.

Событийный сервер:
+0 мс: запросы ставятся в очередь, начинает готовиться первый запрос к БД
+2 мс: уходит первый запрос к БД, начинает готовиться второй +4 мс: уходит второй, начинает готовиться третий
+6 мс: уходит третий, начинает готовиться четвёртый
+8 мс: уходит четвёртый, начинает готовиться пятый
+10 мс: уходит пятый (сервер простаивает)
+12 мс: приходит первый ответ от БД, начинается шаблонизация
+14 мс: приходит второй ответ от БД (ставится в очередь)
+16 мс: приходит третий ответ от БД (ставится в очередь), уходит первый ответ пользователю (шаблонизация первого ответа от БД закончилась), начинается шаблонизация второго ответа от БД
+ 18 мс: приходит четвертый ответ от БД (ставится в очередь)
+ 20 мс: приходит пятый ответ от БД (ставится в очередь), уходит второй ответ пользователю, начинается шаблонизация третьего ответа
+ 24 мс: уходит третий ответ пользователю, начинается шаблонизация четвертого
+ 28 мс: уходит четвертый, начинается шаблонизация пятого
+ 32 мс: уходит пятый

Надеюсь ничего не напутал :)

Минимальное время ответа пользователю 16 мс, максимальное 32, среднее 24, простой <1%. Плюс бонус — распределение нагрузки на СУБД.

Это 5 запросов. А если их будет 10, 100, 1000 одновременных, то разница будет ещё заметнее по среднему времени ответа, а особенно по минимальному (максимальное будет практически равно для таких чисел). Это скромно умалчивая о том, что 100, 1000 воркеров запустить не каждый сервер потянет (по оперативке прежде всего), который легко потянет очередь из 10 000 событий.

Пример, конечно, притянутый за уши (запросы к СУБД выполняются параллельно за фиксированное время независимо от их количества), но, думаю, понятный. Выгода от асинхронности и событийности особенно заметна на большом количестве параллельных запросов, которые ждут внешних событий (вроде «готов ответ от СУБД»). Классический сервер быстро исчерпает пул воркеров (читай — оперативку), а когда пул закончится, то сервер будет простаивать ожидая внешних событий, накапливая запросы в своей очереди (или системной очереди сокета), хотя мог бы в это время делать хоть что-то — отвечать на запросы, не требующих внешних ресурсов, или, хотя бы, готовить запросы к ним. Асинхронный же сервер будет простаивать (копить входящие запросы в системной очереди сокета) только тогда, когда ему абсолютно нечего будет делать (собственная очередь событий будет заполнена).
UFO just landed and posted this here
Какие-то у вас идеальные условия…

+0 мс: все 5 запросов начинают выполняться параллельно
+10 мс: уходит 5 запросов к СУБД (сервер простаивает)
+20 мс: приходит 5 ответов СУБД, начинает выполняться шаблонизация параллельно
+40 мс: уходит 5 ответов на запросы

Во первых, 5 одновременных однотипных запросов БД быстрее выполнит за 1 прогон по индексу. Чем их же, но с паузой достаточной чтобы пришлось званого обратиться к индексу.
Во вторых, если сделать 5 одновременных запросов к mySQL серверу, то ответят они в разное время. И как следствие первый ответ уйдет раньше последнего.
В третьих при высокой нагрузке и реальных условиях все это будет происходить параллельно и в своей очереди, а упираться все в скорость БД.

Событийный сервер:
+0 мс: запросы ставятся в очередь, начинает готовиться первый запрос к БД
+2 мс: уходит первый запрос к БД, начинает готовиться второй +4 мс: уходит второй, начинает готовиться третий
+6 мс: уходит третий, начинает готовиться четвёртый
+8 мс: уходит четвёртый, начинает готовиться пятый
+10 мс: уходит пятый (сервер простаивает)
+12 мс: приходит первый ответ от БД, начинается шаблонизация
+14 мс: приходит второй ответ от БД (ставится в очередь)
+16 мс: приходит третий ответ от БД (ставится в очередь), уходит первый ответ пользователю (шаблонизация первого ответа от БД закончилась), начинается шаблонизация второго ответа от БД
+ 18 мс: приходит четвертый ответ от БД (ставится в очередь)
+ 20 мс: приходит пятый ответ от БД (ставится в очередь), уходит второй ответ пользователю, начинается шаблонизация третьего ответа
+ 24 мс: уходит третий ответ пользователю, начинается шаблонизация четвертого
+ 28 мс: уходит четвертый, начинается шаблонизация пятого
+ 32 мс: уходит пятый
Во четвертых, вы не учли ситуацию когда, при высокой нагрузке у вас будут обрабатываться и входящие запросы и ответы одновременно. В таком случае входящие запросы будут становиться в очередь гораздо медленее.

Я не вижу конструктивных отличий помимо: распределенной нагрузке на БД, меньшим потреблением памяти, большей фрагментации памяти.
Впрочем как и с классическим сервером нагрузка на БД будет такой же в реальных условиях. А временные скачки запросов БД ей только на пользу. Она так может более рационально вернуть данные.
«Правильно, возьмем и увеличим кол-во php процессов, сразу до 20 или 30 и проблема вроде как ушла, хотя на самом деле проблема просто немного отдалилась и момент когда придет 30 запросов Б наступит. Вся беда в том что мы не можем создавать бесконечно много php процессов и путь наращивать их в запредельных количествах неверен.»
Ну если PHP скрипт у нас просто выполняет какой-то запрос и выдаёт результат (т.е. обычная страница), а не висит выдавая по-немного какие-нибудь яваскрипты (например, чат). То не вижу ничего страшного, что если в долю секунды придёт 60 запросов, 20 из них выполнятся за 0.1, следующие 20 в следующие 0.1 и т.п. т.е. на обработку последнего запроса с учётом ожидания уйдёт 0.3 секунды. Поидее производительность нашего сервера будет 20 запросов за 0.1 секунду, т.е. 200 запросов в секунду или 17280000 в день.

Про node.js + mysql — попробовал я протестировать скорость выполнения запросов — оказалось, что на быстрых запросах php+mysql раз в 10 быстрее работает. Сразу же попробовал различные библиотеки к node.js — та же ситуация.

Теперь немножко про ад :) Каждый раз возвращаясь к программированию на php после node.js — ощущаю эйфорию :) Когда можешь сделать так $user->getCountry()->getCurrency()->getCode(), вместо того, чтобы писать

user.getCountry(function(country){
country.getCurrency(function(currency){
console.log(currency.getCode())
})
})

Про ифы и циклы, в которых есть запросы к бд вообще молчу. (доп библиотеки для синхронизации, конечно, немного помогают, но всё равно совсем не элегантно)

В программе которую я переделывал на node.js почему-то выходит, что начиная с подсоединения к веб-серверу — всё должно идти синхронно. т.е. результат подключения к базе данных 99.8% оказывается нужен в следующей строке кода. А зачем тогда все эти колбэки? А вот когда делаешь что-то блокирующее — блокируется уже полностью все коннекты.
Сейчас снова проверил на felixge-node-mysql.

100 запросов типа select, более-менее увесистых (операционка — Ubuntu):
felixge-node-mysql 0.8
php — 0.27

10000 запросов мелких:
felixge-node-mysql — 23.52
php (версия 5.3.6) — 14.04

По вашим бенчмаркам картина похожая. Здесь «в 10 раз», конечно, преувеличено.

А теперь мы сделаем 2 очень мелких запроса:
felixge-node-mysql — 0.109
php — 0.0039

А теперь мы сделаем 5 поувесистей:
felixge-node-mysql — 0.281
php — 0.0149

Здесь получается «в 10 раз» — преуменьшено.

Sannis-node-mysql-libmysqlclient — сейчас не пробовал, но раньше пробовал и там было немного лучше чем felixge-node-mysql, но до пхп всё же очень далеко. Возможно, что-то изменилось с тех пор, либо что-то с настройками, какими-нибудь.

Я надеюсь, что не прав, и дела обстоят лучше, т.к. сам использую и php и node.js
Разница на малом количестве запросов, имхо, из-за того что V8 оптимизирует Javascript по мере выполнения. Попробуйте сделать 2 запроса, потом поставить таймаут и повторить. Имхо, во второй раз результат будет меньше. А это как раз типичный способ использования Node. А для консольных утилит, где Node используется для удобства или из-за наличия необходимых библиотек, лучше взять node-mysql-libmysqlclient — будет быстрее.
Попробовал через таймаут — ситуация действительно улучшилась :)

По-поводу node-mysql-libmysqlclient — пока что переходить не собираюсь, т.к. сайт уже практически закончен, но в felixge-node-mysql есть несколько косяков, которые заметил (максимальный размер пакета 10мб, местами невозможно словить эксепшн) — если они вдруг помешают чему-то либо скорость решу улучшить — перейду, спасибо.
Дайте догадаюсь. У вас все HTTP запросы примерно одинаково тяжелые, а узкое место СУБД — не может де-факто асинхронно отдавать ответы.

Узкое место оно и в Африке узкое место. Асинхронная модель предполагает ощутимый профит в случае если тяжелые неблокирующие запросы встречаются ощутимо реже легких. А если у вас есть блокирующийся на любой запрос ресурс, то вамлюбая асинхронная модель мало поможет. Вроде это очевидно.
У меня узких мест нету :)

Вот в яваскрипте (который в бравзере) программирование выглядит похоже на php в плане — всё идёт сверху вниз. А колбэки если и есть — то как-то незаметны, что-ли. А здесь, со мной происходят неприятные вещи именно из-за того, что очень много работы с базой данных. Если бы как-то можно было автоматом засинхронизировать всё что внутри каждого коннекта к серверу — принесло бы большую радость.

Т.е. было бы так:

io.sockets.on('connection',function(socket){
var user=getLoggedInUserDetails()
var settings = getUserSettingsById(user.id)
})

Но если сделать коннекты к бд блочащими, чтобы getLoggedInUserDetails() — внутри подгребали данные пользователей и возвращали в переменную user. То эта блокировка затронет и других подконнекченных пользователей.

И потом, проблемы с колбэками начинаются сразу же как только начинается более-менее логика насыщенная. Когда разные функции что-то подгребают и т.п.

Я до сих пор не нашел, как нормально цикл сделать, когда внутри колбэки (только извращёнными методами) :)

Например,

getAllCountries(function(countries){
for(var x=0;x
Оборвалось на полуслове…

getAllCountries(function(countries){
for(var x=0;x < countries.length;x++){
getUsersByCountryId(countries[x].id,function(users){
for(var xx=0;xx < users.length;xx++){
console.log(users[x].name)
}
})
}
})

Вот это не проканает (и array.map тоже) :) А что если нам дальше нужно что-нибудь с юзерами сделать?

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

db.online.fetchRows(«select * from users where name = 'aleks_ja'»,function(user){
db.online.fetchRow(«select * from settings where user_id=»+user.id,function(settings){
console.log(settings.news_amount)
})
})
db.online.sync(function(){
console.log('the end')
})

А как бы это выглядело на пхп?

Опять же буду рад если не прав и можно было сделать проще (inner join не предлагать, примеры от-балды для большей наглядности:))
Попробуйте node-sync, он позволяет писать код в синхронном виде, выполняя его асинхронно.
Sign up to leave a comment.

Articles