具有多个并发请求的Node.js服务器,它如何工作? [英] Node.js server with multiple concurrent requests, how does it work?

查看:137
本文介绍了具有多个并发请求的Node.js服务器,它如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道node.js是一个单线程,异步,无阻塞的I/O.我已经读了很多.例如,PHP每个请求仅使用一个线程,而节点仅对所有线程使用一个线程.

I know node.js is a single threaded, asynchronous, non blocking i/o. I've read a lot about that. e.g PHP uses one thread per request but node uses only one thread for all, like that.

假设有三个请求a,b,c同时到达node.js服务器.这些请求中的三个要求进行较大的阻止操作,例如,它们都希望读取相同的大文件.

Suppose there are three requests a, b, c arriving at same time at node.js server. Three of these requests require large blocking operation e.g they all want to read same big file.

然后,如何将请求排入队列,将按什么顺序执行阻塞操作,以及按什么顺序分派响应?当然要使用多少个线程?

Then how are the requests queued, in what sequence will the blocking operation be carried out and in what sequences are the responses dispatched? Of course using how many threads?

请告诉我三个请求从请求到响应的顺序.

Please tell me the sequences from request to response for three requests.

推荐答案

以下是针对您的三个请求的事件序列的描述:

  1. 三个请求发送到node.js Web服务器.
  2. 哪个请求在其他两个请求触发网络服务器请求处理程序之前就到达,并开始执行.
  3. 另外两个请求进入node.js事件队列,等待轮到他们.从技术上讲,是否将等待的请求在传入的TCP级别上排队还是在node.js内部排队(实际上我不知道)取决于node.js实现的内部,但是出于讨论的目的,所有重要的是,传入事件已排队,直到第一个请求停止运行时才会触发.
  4. 第一个请求处理程序将执行,直到遇到异步操作(例如读取文件),然后再执行其他操作,直到异步操作完成为止.
  5. 此时,异步文件I/O操作已启动,并且原始请求处理程序返回了(此操作当时已完成).
  6. 由于第一个请求(正在等待文件I/O)暂时已返回,因此node.js引擎现在可以将下一个事件从事件队列中拉出并启动它.这是到达服务器的第二个请求.它将在第一个请求时经历相同的过程,并将一直运行到无其他事情为止(并且还在等待文件I/O).
  7. 当第二个请求返回系统时(因为它正在等待文件I/O),则第三个请求可以开始运行.它将遵循与前两个相同的路径.
  8. 当第三个请求现在也正在等待I/O并返回系统时,node.js可以自由地将下一个事件从事件队列中拉出.
  9. 这时,所有三个请求处理程序都同时处于运行中"状态.实际上一次只能运行一次,但是所有一次都可以运行.
  10. 事件队列中的下一个事件可能是某个其他事件或某个其他请求,也可能是前三个文件I/O操作之一的完成.队列中下一个事件将开始执行.假设这是第一个请求的文件I/O操作.此时,它将调用与该第一个请求的文件I/O操作关联的完成回调,并且该第一个请求开始处理文件I/O结果.然后,该代码将继续运行,直到完成整个请求并返回,或者开始其他异步操作(如更多文件I/O)并返回为止.
  11. 最终,第二个请求的文件I/O将准备就绪,该事件将从事件队列中拉出.
  12. 然后,对第三个请求相同,最终所有三个请求都将完成.
  1. Three requests are sent to the node.js web server.
  2. Whichever request arrives fractionally before the other two will trigger the web server request handler and it will start executing.
  3. The other two requests go into the node.js event queue, waiting their turn. It's technically up to the internals of the node.js implementation whether a waiting request is queued at the incoming TCP level or whether it's queued inside of node.js (I don't actually know), but for the purposes of this discussion, all that matters is that the incoming event is queued and won't trigger until the first request stops running.
  4. That first request handler will execute until it hits an asynchronous operation (such as reading a file) and then has nothing else to do until the async operation completes.
  5. At that point, the async file I/O operation is initiated and that original request handler returns (it is done with what it can do at that moment).
  6. Since the first request (which is waiting for file I/O) has returned for now, the node.js engine can now pull the next event out of the event queue and start it. This will be the second request to arrive on the server. It will go through the same process at the first request and will run until it has nothing else to do (and is also waiting for file I/O).
  7. When the second requests returns back to the system (because it's waiting for file I/O), then the third request can start running. It will follow the same path as the previous two.
  8. When the third request is now also waiting for I/O and returns back to the system, node.js is then free to pull the next event out of the event queue.
  9. At this point, all three request handlers are "in flight" at the same time. Only one ever actually runs at once, but all are in process at once.
  10. This next event in the event queue could be some other event or some other request or it could be the completion of one of the three previous file I/O operations. Whichever event is next in the queue will start executing. Suppose it's the first request's file I/O operation. At that point, it calls the completion callback associated with that first request's file I/O operation and that first request starts processing the file I/O results. This code will then continue to run until it either finishes the entire request and returns or until it starts some other async operation (like more file I/O) and returns.
  11. Eventually, the second request's file I/O will be ready and that event will be pulled from the event queue.
  12. Then, the same for the third request and eventually all three will finish.

因此,即使实际上只有一个请求实际上同时在执行,多个请求也可以同时处于处理中"或运行中"状态.有时这称为协作多任务,而不是通过具有多个本机线程的抢先式"多任务,系统可以随时在这些线程之间自由切换,给定的Javascript线程运行直到返回系统,然后再返回,直到那时,另一段Javascript才能开始运行.因为一段Javascript可以启动非阻塞异步操作,所以Javascript线程可以在其异步操作仍处于挂起状态时返回到系统(使其他Javascript片段可以运行).这些操作完成后,他们会将事件发布到事件队列中,当其他Javascript完成并且该事件到达队列顶部时,它将运行.

So, even though only one request ever is actually executing at the same time, multiple requests can be "in process" or "in flight" at the same time. This is sometimes called cooperative multi-tasking in that rather than "pre-emptive" multitasking with multiple, native threads where the system can freely switch between threads at any moment, a given thread of Javascript runs until it returns back to the system and then, and only then, can another piece of Javascript start running. Because a piece of Javascript can initiate non-blocking asynchronous operations, the thread of Javascript can return back to the system (enabling other pieces of Javascript to run) while it's asynchronous operations are still pending. When those operations completes, they will post an event to the event queue and when other Javascript is done and that event gets to the top of the queue, it will run.

单线程

此处的关键点是,给定的Javascript线程将运行,直到返回系统为止.如果在执行过程中启动了一些异步操作(例如文件I/O或网络连接),那么当这些事件结束时,它们会将一个事件放入事件队列中,并且当JS引擎完成运行任何事件之前,它,该事件将得到处理,并会导致调用回调,并且该回调将轮流执行.

The key point here is that a given thread of Javascript will run until it returns back to the system. If, in the process of executing, it starts some asynchronous operations (such as file I/O or networking), then when those events finish, they will put an event in the event queue and when the JS engine is done running any events before it, that event will be serviced and will cause a callback to get called and that callback will get its turn to execute.

与多线程模型相比,这种单线程性质极大地简化了并发的处理方式.在一个完全多线程的环境中,每个请求都将启动其自己的线程,然后希望共享的任何数据,甚至是一个简单的变量,都将受到竞争条件的影响,并且必须使用互斥体进行保护,然后任何人都可以读取它.

This single threaded nature vastly simplifies how concurrency is handled vs. a multi-threaded model. In a fully multi-threaded environment where every single request starts its own thread, then ANY data that wishes to be shared, even a simple variable is subject to a race condition and must be protected with a mutex before anyone can even just read it.

在Javascript中,因为没有同时执行多个请求,所以不需要简单的共享变量访问就可以使用互斥体.从定义上看,一段Javascript正在读取一个变量,此时没有其他Javascript在运行(单线程).

In Javascript because there is no concurrent execution of multiple requests, no mutex is needed for simple shared variable access. At the point one piece of Javascript is reading a variable, by definition, no other Javascript is running at that moment (single threaded).

Node.js确实使用线程

一个重要的技术区别是,只有Javascript的执行是单线程的. node.js内部构件确实将线程本身用于某些方面.例如,异步文件I/O实际上使用本机线程.网络I/O实际上并不使用线程(它使用本机事件驱动的网络).

One technical distinction of note is that only the execution of your Javascript is single threaded. The node.js internals do use threads themselves for some things. For example, asynchronous file I/O actually uses native threads. Network I/O does not actually use threads (it uses native event driven networking).

但是,在node.js内部使用线程不会直接影响Javascript执行.一次只能执行一个Java线程.

But, this use of threads in the internals of node.js does not affect the Javascript execution directly. There is still only ever one single thread of Javascript executing at a time.

种族条件

启动异步操作时,在修改过程中仍可能存在状态的竞争条件,但是这种方式比多线程环境中的方式少见,而且更容易识别和保护这些情况.作为可能存在的竞争条件的示例,我有一个简单的服务器,该服务器使用间隔计时器每10秒从多个温度探测器获取读数.它从所有这些温度读数中收集数据,并且每小时将其写出到磁盘中.它使用异步I/O将数据写入磁盘.但是,由于使用了许多不同的异步文件I/O操作将数据写入磁盘,因此间隔计时器可能会在某些异步文件I/O操作之间触发,从而导致服务器所在的数据写到要修改的磁盘的中间.这很不好,可能导致写入不一致的数据.在一个简单的世界中,可以通过在开始将所有数据写入磁盘之前对其进行复制来避免这种情况,因此,如果在将数据写入磁盘时出现新的温度读数,则该副本将不会受到影响,并且代码仍会将一致的数据集写入磁盘.但是,对于这种服务器,数据可能很大,而服务器上的内存却很小(这是Raspberry Pi服务器),因此在内存中复制所有数据是不切实际的.

There still can be race conditions for state that is in the middle of being modified when an async operation is initiated, but this is way, way less common than in a multi-threaded environment and it is much easier to identify and protect these cases. As an example of a race condition that can exist, I have a simple server that takes readings from several temperature probes every 10 seconds using an interval timer. It collects the data from all those temperature readings and every hour it writes out that data to disk. It uses async I/O to write the data to disk. But, since a number of different async file I/O operations are used to write the data to disk, it is possible for the interval timer to fire in between some of those async file I/O operations causing the data that the server is in the middle of writing to disk to be modified. This is bad and can cause inconsistent data to be written. In a simple world, this could be avoided by making a copy of all the data before it starts writing it to disk so if a new temperature reading comes in while the data is being written to disk, the copy will not be affected and the code will still write a consistent set of data to disk. But, in the case of this server, the data can be large and the memory on the server is small (it's a Raspberry Pi server) so it is not practical to make an in-memory copy of all the data.

因此,通过在将数据写入磁盘的过程中设置一个标志,然后在将数据写入磁盘完成后清除该标志,可以解决此问题.如果在设置此标志时触发间隔计时器,则会将新数据放入单独的队列中,并且不会修改正在写入磁盘的核心数据.将数据写入磁盘后,它将检查队列并将发现的任何温度数据添加到内存中的温度数据中.保留了正在写入磁盘的过程的完整性.每当此竞赛条件"被击中并且由于该数据而使数据排队时,我的服务器都会记录一个事件.而且,瞧,它确实每隔一段时间就会发生一次,并且保存数据完整性的代码也可以正常工作.

So, the problem is solved by setting a flag when the data is in the process of being written to disk and then clearing the flag when data is done being written to disk. If an interval timer fires while this flag is set, the new data is put into a separate queue and the core data that is in the process of being written to disk is NOT modified. When the data is done being written to disk, it checks the queue and any temperature data it finds there is then added to the in-memory temperature data. The integrity of what is in the process of being written to disk is preserved. My server logs an event any time this "race condition" is hit and data is queued because of it. And, lo and behold, it does happen every once in a while and the code to preserve the integrity of the data works.

这篇关于具有多个并发请求的Node.js服务器,它如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆