Node.js中Event_pool的使用场景

Author: bugall
Wechat: bugallF
Email: 769088641@qq.com
Github: https://github.com/bugalllinux

一. 说好的单线程异步呢?

众所周知Node是单线程异步,其实这个是相对于Node这层来讲是没有问题的。可是若是总体来看其实仍是有个thread_pool的概念,我更喜欢把Node看作一个胶水层,把libuv与v8粘合在一块儿。v8做为js执行的引擎,libuv封装了一些c++代码来实现一些内核调用,同时把同类功能接口作抽象,知足跨平台的需求。我以为写Node有两条主线,一个是对js语法的使用和理解好比[1]==[1] //false, 另一条主线就是对libuv的理解,由于libuv中的event_loop实现的机制,也是Node在某些场景性能出色的缘由。c++


二. 什么是IO多路复用(max-Multiplexing)

一般咱们若是在建立一个socket请求的时候,内核会为这个socket建立一个标示,咱们一般称为文件描述符(file descriptor),文件描述符实际上是一个索引值,索引值对应的实际存储是一个关于文件的一些元数据的数据结构,好比这个文件的操做权限,建立时间,文件是否可读可写等,总而言之这个结构里存储了一个文件的全部源信息(metadata)。假如咱们如今有10个socket请求进来,首先咱们内核要分配10个文件描述符,那这些被建立的文件描述符必定要有个地方存储才行,在linux有一个文件描述符表是用来管理这些文件描述符信息的,为了方便理解咱们就把这个文件描述符表想象成一个数组(这里须要补充说明下,一般状况硬件相对于内核来讲是异步的),这时候咱们想知道哪些socket有数据到达?比较笨的方法就是枚举每一个文件描述符对应的源数据,而后查看他们对应状态,若是可读咱们就能够从对应的buffer中读取数据,可是这有一个问题,假如咱们的socket请求线性增加,那么遍历一次数组的时间也会跟着线性增加,这种就是poll,select的实现。后来为了解决这个问题有了epollgit


三. 为何epoll的效率高

这里补充一个点,epoll的IO多路复用与iocp的IO多路复用虽然功能看似相同,可是底层的实现原理大相径庭,epoll之因此高效要归功于linux内核的基于事件的实现机制,好比当网卡在加载数据的时候是不会占用cpu的,由于硬件对于内核实现来讲是异步的,当网卡在加载完数据后会发送一个中断给内核,假如你表明一个线程,你在看电视的时候,你妈妈在厨房作饭,当饭作好的时候妈妈会说:“饭好了吃饭了”。那你如今能够选择是过去吃饭仍是继续看电视,至少饭作好了这个事件你是知道的,等你看完电视的时候你会想到还有吃饭这个事情没完成。对于咱们的应用来讲可能会有不少个事件会在未来的某个时间发生,咱们须要把这些事件存储起来,一个一个处理。而windows的内核实现并非基于事件的因此iocp采用的是多线程轮训的方式实现的IO多路复用,因此这也是为何不建议用windows跑Node服务。github

epoll中主要有三个方法epoll_create,epoll_ctl,epoll_wait. create就是建立一个epoll实例,ctl就是准备让epoll监听哪一个文件描述符上的哪些事件,wait获取被激活的事件。按照上面的例子来理解:create就至关于我如今要准备看电视了,准备好本子记录未来要发生时的事情和未处理的事情,ctl就是在本子上记录:当妈妈在作饭的时候,若是妈妈作好了这个事件发生请告诉我,wait就是看下哪些事件被激活,看看妈妈饭有没有作好。epoll_wait中存储的那个被激活的事件列表就是咱们常说的event_pooldocker


四. 既然有了IO多路复用为何还须要thread_pool

上面说了,在libuv中文件操做和DNS解析都是用线程池实现的,接下来咱们看下缘由windows

1. 为何文件操做要用thread_pool

刚开始我也很郁闷这个问题,也在stackoverflow上发了帖子,自己是想刨根问底的从技术实现找到答案的,可是这个没有明确的答案和文档。我本身的想法就是: 其实磁盘IO大多数时间是花在磁盘寻道上,一旦开始读取数据读写量会很大,不像socket网络延迟高,每次收到的IP分段后的数据包小,当buffer被写满后才会通知内核处理,因此socket是能够用epoll来实现的。可是文件一旦开始读取,那么buffer瞬间会被写满,epoll中的被激活的事件列表中一直会有这个文件可读这个事件。若是是一个很是大的文件的话会形成epoll的event_pool阻塞。数组

2. 为何DNS解析也要用thread_pool

一般咱们在发送http请求的时候首先须要对host进行解析获取域名对应的ip的地址,这样咱们才能真正的发送请求。因此说这个过程必定是个同步的过程,由于在DNS解析没有成功的时候发送http请求是没有意义的。在http中默认调用的是dns.lookup方法。这个方法会去读取/etc/hosts这个文件,我想而后查本地的DNS缓存,若是都没有而后再发请求向DNS解析服务器查询。这个查询有个超时时间,若是dns解析失败那么这个请求也就失败了。缓存

由于我负责的项目天天有3000w的外网http请求和3亿次的文件读写(用Node写我也是醉了)。天天凌晨docker重启容器的时候会形成本地缓存的DNS失效,而后瞬间大量的外网http请求须要走DNS解析,Node默认设置libuv的thread_pool是3,结果在重启完成的几分钟内会有不少报错。后来我把线程调整到128明显好不少。服务器


五. Node使用多进程的意义大不大

这个问题我如今也没有明确的认知和测试结果。后期我会尽快完成。网络

  • 能够确定的是,多进程对epoll没有影响。

  • 对thread_poll更没有多大影响,由于使用thread_poll的场景是文件操做与DNS解析这两个

  • 由于文件操做的性能取决于磁盘性能,就算你有16核16个进程,当磁盘到达性能阈值值的时候再多核也没有意义。

  • 惟一能提高性能的地方就是主线程。也就是v8执行这里,由于咱们从event_loop中获得的被激活的事件是为了作一些事件对应的逻辑也就是咱们所说的callback,咱们会把这些被激活的事件对应的callback放到一个叫作任务队列的地方中去,主线程处理的就是任务队列的东西,假如咱们把一个任务队列须要处理的code分散到4个进程和4个任务队列中去性能是有提升的。

相关文章
相关标签/搜索