⬆️ ⬇️

nodeJS and nonblocking I / O

Good evening, dear readers,



On Habré recently there were a few mentions of node, a fast platform for creating web applications in javascript, which has a rather unique to date feature, non-blocking I / O (input / output).



To begin with about nodeJS:



1) JavaScript is used as a language.

2) To execute JavaScript, the V8 engine from Google is used, which works fairly quickly due to compiling into machine code before execution.

3) To implement non-blocking I / O, libev and libeio are used (libev shows better results than libevent)

4) The commonJS syntax is selected to implement the library connection.

5) It is possible to write modules also in C / C ++, while they are connected in exactly the same way as js modules, this will allow you, for example, to rewrite any bottleneck in your application in C ++ without any difficulties.



In the process of studying this moment, which eventually led to the writing of this article, I managed to play pretty well with this particular feature. And I can say that non-blocking I / O has a very, very positive effect on performance and resistance to stress.





')

Consider three schemes:



1) Multiprocessor models (For each user in the process)

2) Single Process Event-Loop

3) Single Process Event-Loop with Non-Blocking I / O (NodeJS)



Suppose there is only one processor with one core in our system (If there are more cores, we can simply run several instances of our application)



And now about each one more specifically:



1) Multi-process application
With large loads (large number of connections) it will show very bad results, since each user will be assigned a separate process, as a result, a lot of RAM will be consumed, and the processor will be overloaded, as a change of processes is a rather expensive operation



2) Single Process Event-Loop
On the one hand, such an approach would be better to behave under loads, because the process is only one, which means that unnecessary random access memory and extra calculations for changing processes are not required, but everything is not so simple, because every time we read a file in our application, or we make a request to the database, the entire event loop stops and waits for a response from the hard disk or server, while the processor does nothing at this time, the efficiency drops seriously



3) Single Process Event-Loop with Non-Blocking I / O
This approach combines the advantages of the first two, we do not spend extra memory, do not do unnecessary calculations to change processes, but the application does not stand idle while we read the file, we turn to DB, etc. ... However, this method has one serious drawback. Developing such applications is very difficult. It was! But now there is a nodeJS!



Here is the simplest example from the official site:

var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  1. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  2. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  3. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  4. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  5. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  6. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  7. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  8. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  9. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;
  10. var sys = require ( 'sys' ) , http = require ( 'http' ) ; http. createServer ( function ( req , res ) { setTimeout ( function ( ) { res. sendHeader ( 200 , { 'Content-Type' : 'text/plain' } ) ; res. sendBody ( 'Hello World' ) ; res. finish ( ) ; } , 2000 ) ; } ) . listen ( 8000 ) ; sys. puts ( 'Server running at 127.0.0.1:8000/' ) ;


It's simple, the script waits 2 seconds and sends you Hello World. It would seem a very simple task, but let's write for example in python.

Take a well-proven library, tornado (most likely the fastest to date) and write:



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. class MainHandler (tornado.web.RequestHandler):
  7. def get (self):
  8. time .sleep (2)
  9. self. write ("Hello World")
  10. application = tornado.web.Application ([
  11. (r "/", MainHandler),
  12. ])
  13. if __name__ == "__main__":
  14. http_server = tornado.httpserver.HTTPServer (application)
  15. http_server.listen (8888)
  16. tornado.ioloop.IOLoop.instance (). start ()




It turned out also quite short and elegant.



We are testing (10 users suddenly turned to our web server, it can be more, but ApacheBench is not as patient as I ..):



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




I didn’t want to offend Python, because the whole thing is that time.sleep is a blocking function, that is, it blocks the entire event loop, just as the event loop in python is blocked by almost everything related to input and output, and only advanced programmers know how to do it. or another action without blocking the interpreter, and often davolno solutions for this rather cumbersome and ugly.




Of course, two examples are absolutely not equivalent, but writing an asynchronous code on Python will require more effort than a node simply because all standard calls are asynchronous to the node ... And even a novice will write fast code on it.



I think everything became clear ... And I have no doubt that the Python programmer will quickly rewrite my example using threads, so that it works as it should, only the code will be 2 times larger, and then raise the hands of those who each file request The system or the database draws in a separate thread!



Instead of completion



Non-blocking I / O is part of the nodeJS philosophy. Working with files, working with TCP, HTTP, DNS, communicating with the system, communicating with other processes, all this does not block input and output. And I realized the beauty of it when I wrote my hashlib library for node. While doing the non-blocking function md5_file, I spent a little experiment. The fact is that taking md5 from a file is a very slow procedure, for example, a movie is considered 5–15 minutes (depending on its size) and as it turned out 80% of the time is spent not on calculating md5, but on reading the file, respectively using my library extract md5 from 5 films at the same time, for the same time for which you extract only one using standard tools.



You can find a lot of useful stuff here: nodejs

Source: https://habr.com/ru/post/83865/



All Articles