有多个并发请求的Node.js服务器,它是如何工作的?

我知道node.js是一个单线程,asynchronous,非阻塞I / O。 我读了很多关于这个。 例如,PHP每个请求使用一个线程,但节点只使用一个线程,就像这样。

假设有三个请求a,b,c同时到达node.js服务器。 其中三个请求需要大量的阻塞操作,例如他们都想要读取同一个大文件。

那么这些请求是如何排队的,按照什么样的顺序进行阻塞操作,以及以什么顺序发送响应呢? 当然使用多less个线程?

请告诉我从请求到响应三个请求的序列。

以下是三个请求的一系列事件的描述:

  1. 三个请求被发送到node.js Web服务器。
  2. 无论哪个请求在另外两个请求到达之前都会触发Web服务器请求处理程序,并且它将开始执行。
  3. 其他两个请求进入node.js事件队列,等待轮到他们。 从技术angular度讲,node.js实现的内部是等待的请求是否在传入的TCP层次上排队,或者是否在node.js(实际上我不知道)中排队,但是为了讨论的目的,所有重要的是传入的事件是排队的,不会触发,直到第一个请求停止运行。
  4. 该第一个请求处理程序将执行,直到它遇到asynchronous操作(如读取文件),然后没有别的事情要做,直到asynchronous操作完成。
  5. 在这一点上,asynchronous文件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操作关联的完成callback,并且第一个请求开始处理文件I / O结果。 这段代码将继续运行,直到它完成整个请求并返回,或者直到它开始一些其他asynchronous操作(如更多的文件I / O)并返回。
  11. 最后,第二个请求的文件I / O将准备就绪,该事件将从事件队列中拉出。
  12. 然后,第三个要求也是一样的,最后三个要完成。

因此,即使只有一个请求在同一时间实际执行,多个请求可以同时在“正在处理”或“正在运行”。 这有时被称为协作式多任务,而不是“先发制人”的多任务处理,具有多个本地线程,系统可以随时在线程之间自由切换,给定的Javascript线程运行,直到返回到系统,然后,只有这样,另一块Javascript才能开始运行。 因为一段Javascript可以启动非阻塞的asynchronous操作,所以Javascript的线程可以返回到系统(启用其他Javascript脚本),而asynchronous操作仍在等待处理。 当这些操作完成时,他们会将事件发送到事件队列,当其他Javascript完成,并且该事件到达队列顶部时,它将运行。

单螺纹

这里的关键是,一个给定的Javascript线程将运行,直到它返回到系统。 如果在执行过程中启动了一些asynchronous操作(比如文件I / O或者networking连接),那么当这些事件完成时,它们会在事件队列中放置一个事件,当JS引擎完成之前运行任何事件这个事件将被服务,并且会导致一个callback被调用,这个callback将被执行。

这种单线程本质极大地简化了并发处理与multithreading模型的对比。 在一个完全multithreading的环境中,每个请求都启动它自己的线程,那么任何希望被共享的数据,即使是一个简单的variables都会受到竞争条件的影响,并且必须使用互斥体进行保护才能读取。

在Javascript中,因为没有多个请求的并发执行,所以简单的共享variables访问不需要互斥体。 在一点Javascript正在读取一个variables,根据定义,当时没有其他的Javascript正在运行(单线程)。

Node.js使用线程

注意的一个技术区别是只有你的Javascript的执行是单线程的。 node.js内部使用线程本身来做一些事情。 例如,asynchronous文件I / O实际上使用本地线程。 networkingI / O实际上不使用线程(它使用本地事件驱动的networking)。

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

比赛条件

在启动asynchronous操作时,状态仍然存在竞争状态,但是这种方式不如multithreading环境中常见,并且更容易识别和保护这些情况。 作为一个可以存在的竞争条件的例子,我有一个简单的服务器,使用间隔计时器每隔10秒从多个温度探测器读取数据。 它从所有这些温度读数收集数据,并且每小时将数据写入磁盘。 它使用asynchronousI / O将数据写入磁盘。 但是,由于使用了许多不同的asynchronous文件I / O操作将数据写入磁盘,所以间隔计时器可能会在引起服务器所在数据的那些asynchronous文件I / O操作之间触发写入到磁盘中间进行修改。 这是不好的,可能会导致不一致的数据被写入。 在一个简单的世界里,可以通过在将数据写入磁盘之前制作所有数据的副本来避免这种情况,因此如果在数据写入磁盘时有新的温度读取,副本不会受到影响,代码仍然会将一组一致的数据写入磁盘。 但是,在这个服务器的情况下,数据可能很大,服务器上的内存很小(这是一个Raspberry Pi服务器),所以在内存中复制所有数据是不现实的。

所以,当数据正在写入磁盘时,通过设置一个标志来解决问题,然后在数据写入磁盘时清除标志。 如果在设置此标志时触发间隔定时器,则将新数据放入单独的队列中,并且正在写入磁盘的核心数据不会被修改。 当数据完成写入磁盘时,它会检查队列,并将其中find的所有温度数据添加到内存中的温度数据。 保存正在写入磁盘的过程的完整性。 任何时候这个“竞争条件”被击中,我的服务器都会logging一个事件,并且数据因此而排队。 而且,瞧,它每隔一段时间就会发生一次,代码保存了数据的完整性。