NodeJS事件驱动-时间轴并发


NodeJS-时间轴的并发

NodeJS最大的优势是在高并发下的高吞吐量,这得益于它最大的特色事件驱动和异步,看过一篇文章介绍NodeJS成也在此败也在此。大致整理一下我对它的理解,放在这里以备后用。

先说一下线程并发模型,然后进一步说说NodeJS的时间驱动单进程模型带来的优势。

并发的线程(进程)模型

为了解释程序的运行过程假设存在有这么一个业务流程:

  1. 程序接受请求,开始从网络拉去数据CPUt1
  2. 客户端从网络发送请求数据时间耗时 IOt1
  3. 程序分析请求,并发送数据库请求耗时需要CPUt2
  4. 程序获取数据库返回数据时间IOt2
  5. 程序分析结果,构造返回结果耗时CPUt3
  6. 程序向网络发送结果耗时IOt3

这个是典型的数据查询操作,这个业务流程需要的时间位T=IOt[1,2,3]+CPUt[1,2,3],另外在多线程环境下还要考虑一个线程上下文切换时间sc,当然线程内堆栈切换、现场保护也是需要时间的但总台来说进程切换消耗>线程间切换时间>线程内上下文切换时间。

假设硬件是单CPU系统,同理多CPU就是多几套单CPU结构。

  1. 单线程程序

    接收到一个处理请求程序按照预定的命令序列依次执行。 当程序运行到IO等操作时会等待其完成,此时间段不进行其它的工作,此时程序可能放弃CPU, 但此时只有一个线程运行,那么CPU是空闲的,这是一个硬件资源的浪费。 程序每处理一个请求,当前程序请求完成后继续处理下一个请求。

    那么每个业务时间CPUt+IOt,程序整体单位时间的吞吐量就是1/T

  2. 多线程程序

    当程序接受到一个请求后,会分配给一个线程去执行, 然后继续接受下一个请求,分配给新的线程。 当线程运行到IO操作时,此线程会放弃CPU,发生一次线程间切换操作, 当前操作等待IO换到其它需要需要CPU运算的线程。

    不考虑线程切换时间sc,假设让CPU满负载, 那么程序应该每当一个线程运行完需要CPU运算的部分就正好切换上另外一个线程, 那么CPU的吞吐量1/CPUt,也就是说系统的吞吐量收CPUt影响最大。 此时需要的并发线程数就是CPUt,假设的CPUt=2ms,那么程序的理论每秒吞吐量为500req, 线程并发数是500。但是如果单核真的开启500个线程,那么系统的会花大量的时间去维护线程间 切换,那么sc时间会大大增加,系统的吞吐量将远远小于500。

    在多线程环境下其实的业务流程时间应该为CPUt+IOt+5sc,也就是每次请求要多5个sc的时间, 所以处理500req需求的时间就不再是1s。而且随着并发线程数的增多么次sc的时间是增加的。 其实线程数的最佳数值基本是固定的范围内波动的,估算应该是略低于内核数/业务平均CPUt。 虽然可以通过优化算法缩短CPUt,但随着CPUt的优化,就需要更多的并发线程, 那么sc耗时就不能忽略,可能优化的CPUt获得的优势反倒会被sc时间抵消。 也就是说缩短CPUt优化会受到上下文切换的瓶颈,再说如果计算本身很简单也没什么优化空间。

    当前我的系统就是一个并发数比较高、每次CPUt比较小(数据处理很简单)的系统, 在高并发的情况下,通过再增加Tomcat的线程数也起不到很好的效果。


NodeJS的单进程事件驱动模型

在上文介绍了单线程模式,提到了单线程(进程)存在资源浪费导致系统的吞吐量变低。 而NodeJS程序虽然也是单进程模式,反倒有了更大的吞吐量这就得益于他的事件驱动模型。

在进程中事件派发线程负责收集事件,当有请求到达时程序时程序就执行业务的1号操作,1号操作执行完成了,NodeJS并不是想单线程一样傻等,而是接着去看看有没有新的事件发生,在高并发的情况下新请求在排队,那么很快NodeJS程序就会继续处理1号操作;当第一个请求的IO发生完成的时候,它也会触发一个事件这个事件也去排队,程序处理完了手头的东西一低头看见原来第一个请求的IO操作完成了,那我就为他做3号操作。之所以NodeJS程序知道2号IO操作执行完成了应该做第三号操作,是因为在为他安排2号工作时就告诉了它说“当2号操作完成后做….”,这指示我们通过回调函数完成。比如下面一个Mongodb的操作流程被划为成了,打开数据库回调->打开Collection回调->执行数据操作回调。这样NodeJS是靠某件事情发生了去做什么安排程序运行的,而不是像原来一样程序一口气跑到黑,这就是事件驱动了。这样做有个好处就是我们的程序的一个线程不再是做一个请求,而是能同时处理处理多个请求。这样达到了多线程的效果而且没有线程间切换(至少很少)。

如上所述因为线程一直在忙,只要有事就在做而且不等待IO不切换线程,所以宏观上看到的请求处理时间为CPUt+IOt,但是CPU看到的是一直有CPUt需要处理因为等待IOt的时间有下一个CPUt需要处理。因为没有线程间切换所有也就没有了sc的时间,所以我们此时的吞吐瓶颈将是1/CPUt。也正是因此NodeJS不适合于CPU运算密集性的系统,它更适合CPUt很小,IOt很大的系统。

本文采用CC BY-SA许可发布,您可以自由的转载分享。

转载请保留出处 BeanMr.
http://blog.beanmr.com/2014/08/01/NodeJS-EventDrive/


 Toc