Pull to refresh

nodeJS и nonblocking I/O

Reading time 5 min
Views 9.3K

Добрый вечер уважаемые читатели,


На хабре последнее время проскакивало несколько упоминаний о node, быстрой платформе для создания вэб приложений на javascript, обладающей довольно уникальной на сегодняшний день особенностью, неблокирующим I/O (вводом/выводом).

Для начала о nodeJS:


1) В качестве языка используется JavaScript
2) Для выполнения JavaScript используется движок V8 от Google, который работает довольно быстро благодаря компиляции в машинный код перед выполнением.
3) Для реализации неблокирующего I/O используются libev и libeio, (libev показывает более хорошие результаты по сравнению с libevent)
4) Для реализации подключения библиотек выбран синтаксис commonJS
5) Есть возможность писать модули ещё и на C/C++, при этом подключаются они точно так-же как и js модули, это позволит Вам, к примеру, любое узкое место в вашем приложении переписать на C++ без каких либо трудностей.

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



Рассмотрим три схемы:


1) Мультипроцессорные модели (На каждого пользователя по процессу)
2) Однопроцессный Event-Loop
3) Однопроцессный Event-Loop с неблокирующим I/O (NodeJS)

Предположим в нашей системе всего один процессор с одним ядром (Если ядер больше — мы можем просто запустить несколько экземпляров нашего приложения)

А теперь о каждом по конкретнее:

1) Много-процессное приложение
При больших нагрузках (большом количестве подключений) покажет очень плохие результаты, так как на каждого пользователя будет отводиться отдельный процесс, вследствие чего будет расходоваться много оперативной памяти, а также процессор будет перегружен, так как смена процессов — довольно дорогостоящая операция 

2) Однопроцессный Event-Loop
С одной стороны такой подход будет лучше вести себя под нагрузками, ведь процесс всего один, а значит не требуется лишняя оперативная память, и лишние вычисления по смене процессов, однако всё не так просто, ведь каждый раз, когда мы в нашем приложении читаем файл, или делаем запрос к БД, весь event loop останавливается и ждёт ответа от жёсткого диска или сервера, процессор же в это время не делает ничего, эффективность серьёзно падает

3) Однопроцессный Event-Loop с неблокирующим I/O
Этот подход совмещает в себе плюсы первых двух, мы не расходуем лишнюю память, не делаем лишних вычислений для смены процессов, но при этом приложение не простаивает пока мы читаем файл, обращаемся к бд итд… Однако такой метод имеет один серъёзный недостоток. Разрабатывать такие приложения очень сложно. Было! Но теперь есть nodeJS!

Вот самый простой пример с официального сайта:
  1. var sys = require('sys'),
  2.      http = require('http');
  3. http.createServer(function (req, res) {
  4.   setTimeout(function () {
  5.     res.sendHeader(200, {'Content-Type': 'text/plain'});
  6.     res.sendBody('Hello World');
  7.     res.finish();
  8.   }, 2000);
  9. }).listen(8000);
  10. sys.puts('Server running at 127.0.0.1:8000/');

Всё просто, скрипт ждёт 2 секунды и отправляет Вам Hello World. Казалось бы очень простая задача, но давайте напишем например на python.
Возмём  хорошо зарекомендовавшую себя библиотеку, tornado (скорее всего самую быструю на сегодняшний день) и напишем:

Copy Source | Copy HTML
  1. #!/usr/bin/env python
  2. import tornado.httpserver
  3. import tornado.ioloop
  4. import tornado.web
  5. import time
  6.  
  7. class MainHandler(tornado.web.RequestHandler):
  8.     def get(self):
  9.         time.sleep(2)
  10.         self.write("Hello World")
  11.  
  12. application = tornado.web.Application([
  13.     (r"/", MainHandler),
  14. ])
  15.  
  16. if __name__ == "__main__":
  17.     http_server = tornado.httpserver.HTTPServer(application)
  18.     http_server.listen(8888)
  19.     tornado.ioloop.IOLoop.instance().start()


Получилось тоже довольно коротко и элегантно.

Тестируем (10 пользователей внезапно обратилисьна к нашему вэб серверу, можно и больше, но ApacheBench не такой терпеливый как я..):

Python, Tornado


ab -n 10 -c 10 http://127.0.0.1:8888/
Time taken for tests:   20.078 seconds


nodeJS


ab -n 10 -c 10 http://127.0.0.1:8000/
Time taken for tests:   2.007 seconds


Ни в коем случае не хотел обидеть Питон, ведь всё дело в том что time.sleep блокирующая функция, тоесть блокирует весь event loop, точно также event loop в питоне блокируется практически всем что связано с вводом выводом, и только продвинутые программисты знают как сделать то или иное действие не заблокировав интерпретатор, и давольно часто решения для этого довольно громоздкие и некрасивые.


Разумеется два примера совершенно не равноценны, однако написание ассинхронного кода на Питоне потребует больше усилий, чем на node просто потому что на node все стандартные вызовы ассинхронные… И даже новичёк будет писать на нём быстрый код.

Я думаю всё стало ясно… И я не сомневаюсь, что Питон программист быстренько перепишет мой пример используя threads, так, чтобы он работал как надо, вот только кода станет в 2 раза больше, да и потом поднимите руки те, кто каждый запрос к файловой системе или к базе данных оформляет в отдельный тред!

Вместо завершения


Неблокирующий ввод вывод — часть философии nodeJS. Работа с файлами, работа с TCP, HTTP, DNS, общение с системой, общение с другими процессами, всё это не блокирует ввод вывод. А прелесть этого я осознал, когда писал свою библиотеку hashlib для node. Делая неблокирующую функцию md5_file я провёл небольшой эксперемент. Дело в том что взятие md5 от файла — процедура весьма небыстрая, например фильм считается 5–15 минут (смотря какого он размера) и как оказалось 80% времени тратится совсем не на вычисление md5, а на чтение файла, соответственно используя мою библиотеку вы можете извлечь md5 от 5 фильмов одновременно, за то же время за которое извлечёте всего один используя стандартные средства.

Много полезного вы сможете найти тут: nodejs
Tags:
Hubs:
+26
Comments 105
Comments Comments 105

Articles