node.js
- Node.js是一个基于Chrome V8引擎的JavaScript运行环境
- Node.js使用事件驱动、非阻塞式I/O模型,使其轻量且高效。
- Node.js包管理器npm是全球最大的开源库生态系统
Ryan Dahl在开始编写一些开源项目帮助客户解决Web服务器高并发性能问题,尝试过的语言有Ruby、C、Lua,这些尝试虽然都最终失败了,只有其中通过C编写的HTTP服务库libebb项目略有起色,基本上算是libuv的前身。这些失败各有各的原因,Ruby因为虚拟机性能太烂而无法解决根本问题,C代码性能高但让业务通过C进行开发显示是不太现实的事情,Lua已有的同步I/O导致无法发挥性能优势。虽然经历了失败但Ryan Dahl大致的感觉到了解决问题的关键是要通过事件驱动和异步I/O来达到目的。当他快要绝望的时候,V8引擎来了,V8满足了他关于高性能Web服务器的现象:
- 没有历史包袱,没有同步I/O,不会出现一个同步I/O导致事件循环性能急剧降低的情况。
- V8性能足够好,远远比Python、Ruby等其他脚本语言的引擎快。
- JavaScript语言的闭包特性非常方便,比C中的回调函数好用。
于是在2009年2月,按新的想法他提交了项目的第一行代码,这个项目名字最终被定义为node
。
Node.js是一个专注于实现高性能的Web服务器优化的专家,几经探索,几经挫折后,遇到V8而诞生的项目。
libuv
- Node.js底层库是使用C语言编写libuv库,为了解决跨平台异步I/O读写。
- libuv是一个高性能事件驱动大的异步IO库,本身是由C语言编写,具有很高的可移植性。
- libuv封装了不同平台底层对于异步IO模型的实现,其本身具备着Windows、Linux都可使用的跨平台能力。
- libuv是一个跨平台聚焦于异步IO的库,著名的event-loop便是libuv的一大特色。
- libuv专为Node.js而设计,后因事件驱动的异步IO的高效逐渐被其他语言和项目采纳而多为自身的底层库使用。
Node.js刚出来时底层采用的并不是libuv而是libev,libev本身也是一个异步IO库,但只能在POSIX系统下使用。随着Node.js被越来越多人使用,而且Windows用户量巨大所以开始考虑Node.js跨平台能力。
此时Node.js提供了libnv作为抽象封装层,在UNIX系统上通过封装libev和libio来调用Linux的epoll或kqueue,在Windows上的IOCP进行封装,自此之后Node.js具备了跨平台能力。由libuv作为中间层本身提供的跨平台的抽象,来根据系统决定更实用libev/libio或IOCP。在Node.js v0.9.0版本中libuv移除了libev的内容。
node.jslibuv的特性
使用C语言实现的异步事件网络库libevent、libev、libuv
- libevent 应用最为广泛、历史悠久的跨平台事件库
- libev 较libevent而言设计更为简练、性能更好,但对Windows支持不够友好。
- libuv 开发node过程中需要跨平台事件库,最初首选libev但需支持windows,后经封装linux下使用libev,windows下使用IOCP,而形成的libuv库。
libuv采用了异步、事件驱动的编程风格,其主要任务是为开发人员提供一套事件循环和基于I/O通知的回调函数。
libuv提供了一套核心的工具集,比如定时器、非阻塞网络编程的支持,异步访问文件系统,子进程等功能。
libuv支持的特性
- 基于操作系统支持的事件循环机制
- Linux下基于epoll实现
- Windows下基于IOCP实现
- MacOS/UNIX下基于kqueue实现
- FreeBSD下基于evports实现
- 异步TCP/UDP通信
- 异步DNS解析
- 异步文件读写
- 文件系统事件机制
- 基于ANSII控制字符的TTY
- IPC、管道通信
- 多进程、线程池
- 信号量处理
- 高分辨率始终
- 多线程及同步操作
异步
程序最基本的活动是对输入输出的处理而不是大量的数值计算,传统输入输出函数诸如read
、fprintf
等的问题是阻塞的,也就是说将数据写入磁盘或从网络读取数据时都会消耗大量时间,阻塞函数直到任务完成后才会返回,在此期间程序什么都不能做,此时及浪费大量的CPU时间。对于追求高性能的程序而言,在其他活动或I/O操作进行时应该尽量让CPU不被阻塞。标准的解决方案是使用线程,每个阻塞的IO操作都在一个单独的线程或线程池中启动。但阻塞函数被调用时,处理器可以调度另外一个真正需要CPU的线程来执行任务。
libuv采用另一种方式来处理阻塞任务,即异步和非阻塞的方式。现代操作系统都提供了事件通知功能。比如调用read
读取网络socket是会阻塞,直到发送者最终发送了数据,read
才会返回。但是应用程序可以要求操作系统监控socket,并在socket上注册事件通知。应用程序可以在适当的时候查看它所监视的事件并获取数据。整个过程是异步的,因为程序在某个时刻关注了它感兴趣的事件,并在另一个时刻获取或使用数据。这也是非阻塞的。因为该进程还可以处理另外的任务。
libuv的事件循环方式很好地与该模型匹配,因为操作系统事件可以视为另外一种libuv事件,非阻塞方式可以保证在其他事件到来时被尽快处理。
事件循环
在事件编程模型中,应用程序通常会关注某个特定事件,并在事件发生后对其作出响应,而收集事件或监控其他事件源则是libuv的职责。编程人员只需要对感兴趣的事件注册回调函数,在事件发生后libuv将会调用相应的回调函数。主要程序不退出,事件循环通常会一直运行。
适用于事件驱动编程模型的场景
- 文件已经准备好可写入数据
- 某个socket上存在数据可读
- 定时器已超时
网友评论