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

Sid*_*rth 17 nonblocking single-threaded node.js

我知道node.js是一个单线程,异步,非阻塞的i/o.我已经阅读了很多相关内容.例如,PHP每个请求使用一个线程,但节点只使用一个线程,就像那样.

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

那么请求如何排队,执行阻塞操作的顺序以及调度响应的顺序是什么?当然使用了多少线程?

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

jfr*_*d00 42

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

  1. 三个请求被发送到node.js Web服务器.
  2. 无论哪个请求在其他两个请求之前到达,将触发Web服务器请求处理程序,它将开始执行.
  3. 其他两个请求进入node.js事件队列,等待轮到他们.从技术上来说,在node.js实现的内部是否等待请求在传入的TCP级别排队,或者它是否在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. 然后,同样的第三个请求,最终所有三个将完成.

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

单螺纹

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

这种单线程特性大大简化了并发处理与多线程模型的对比.在一个完全多线程的环境中,每个请求都会启动自己的线程,那么任何希望共享的数据,即使是一个简单的变量都会受到竞争条件的影响,必须先用互斥锁保护,然后才能读取它.

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

Node.js使用线程

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

但是,在node.js内部使用线程不会直接影响Javascript的执行.仍然只有一个Javascript执行的单个线程.

比赛条件

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

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

  • @jackblank - I/O表示输入/输出.它是从外部源(磁盘,网络或其他外围设备)读取或写入数据的快捷方式.`Initiated`表示node.js告诉某个子系统开始在后台读取文件,而其他处理继续(这就是异步读取的工作原理).然后,子系统将在读取数据时调用回调. (3认同)