从开始学习 node 到现在已经有半年多了, 中间没有做过什么实际工作中的项目, 所以感觉自己的知识有些匮乏, 但是我还是要写这些文章, 因为工作中的需要用 node 来开发后台环境, 再加上我对这些知识记得不多, 都是来看以前写的源码抄过来, 自己根本记不住一些繁琐的代码, 想借此机会来巩固一下我所学到的东西, 等以后慢慢来补充我现在所写的文章.
可以去百度 node.js 中文网, 上面所写的第一句话就是这么一句概括了 node.js 所有的话吧.
node.js 是一个基于 Chorme V8 引擎的 javascript 运行环境. node.js 使用了一个事件驱动, 非阻塞式 I/O 模型, 使其轻量又高效.
什么是 Chorme V8 引擎, 它是怎么来的? 此处直达廖雪峰大佬的文章 https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434501245426ad4b91f2b880464ba876a8e3043fc8ef000
Node.js 是目前非常火热的技术, 但是它的诞生经历却很奇特.
众所周知, 在 Netscape 设计出 JavaScript 后的短短几个月, JavaScript 事实上已经是前端开发的唯一标准.
后来, 微软通过 IE 击败了 Netscape 后一统桌面, 结果几年时间, 浏览器毫无进步.(2001 年推出的古老的 IE 6 到今天仍然有人在使用!)
没有竞争就没有发展. 微软认为 IE6 浏览器已经非常完善, 几乎没有可改进之处, 然后解散了 IE6 开发团队! 而 Google 却认为支持现代 web 应用的新一代浏览器才刚刚起步, 尤其是浏览器负责运行 JavaScript 的引擎性能还可提升 10 倍.
先是 Mozilla 借助已壮烈牺牲的 Netscape 遗产在 2002 年推出了 Firefox 浏览器, 紧接着 Apple 于 2003 年在开源的 Khtml 浏览器的基础上推出了 WebKit 内核的 Safari 浏览器, 不过仅限于 Mac 平台.
随后, Google 也开始创建自家的浏览器. 他们也看中了 WebKit 内核, 于是基于 WebKit 内核推出了 Chrome 浏览器.
Chrome 浏览器是跨 Windows 和 Mac 平台的, 并且, Google 认为要运行现代 Web 应用, 浏览器必须有一个性能非常强劲的 JavaScript 引擎, 于是 Google 自己开发了一个高性能 JavaScript 引擎, 名字叫 V8, 以 BSD 许可证开源.
现代浏览器大战让微软的 IE 浏览器远远地落后了, 因为他们解散了最有经验, 战斗力最强的浏览器团队! 回过头再追赶却发现, 支持 HTML5 的 WebKit 已经成为手机端的标准了, IE 浏览器从此与主流移动端设备绝缘.
浏览器大战和 Node 有何关系?
话说有个叫 Ryan Dahl 的歪果仁, 他的工作是用 C/C++ 写高性能 Web 服务. 对于高性能, 异步 IO, 事件驱动是基本原则, 但是用 C/C++ 写就太痛苦了. 于是这位仁兄开始设想用高级语言开发 Web 服务. 他评估了很多种高级语言, 发现很多语言虽然同时提供了同步 IO 和异步 IO, 但是开发人员一旦用了同步 IO, 他们就再也懒得写异步 IO 了, 所以, 最终, Ryan 瞄向了 JavaScript.
因为 JavaScript 是单线程执行, 根本不能进行同步 IO 操作, 所以, JavaScript 的这一 "缺陷" 导致了它只能使用异步 IO.
选定了开发语言, 还要有运行时引擎. 这位仁兄曾考虑过自己写一个, 不过明智地放弃了, 因为 V8 就是开源的 JavaScript 引擎. 让 Google 投资去优化 V8, 咱只负责改造一下拿来用, 还不用付钱, 这个买卖很划算.
于是在 2009 年, Ryan 正式推出了基于 JavaScript 语言和 V8 引擎的开源 Web 服务器项目, 命名为 Node.js. 虽然名字很土, 但是, Node 第一次把 JavaScript 带入到后端服务器开发, 加上世界上已经有无数的 JavaScript 开发人员, 所以 Node 一下子就火了起来.
在 Node 上运行的 JavaScript 相比其他后端开发语言有何优势?
最大的优势是借助 JavaScript 天生的事件驱动机制加 V8 高性能引擎, 使编写高性能 Web 服务轻而易举.
其次, JavaScript 语言本身是完善的函数式语言, 在前端开发时, 开发人员往往写得比较随意, 让人感觉 JavaScript 就是个 "玩具语言". 但是, 在 Node 环境下, 通过模块化的 JavaScript 代码, 加上函数式编程, 并且无需考虑浏览器兼容性问题, 直接使用最新的 ECMAScript 6 标准, 可以完全满足工程上的需求.
node 是啥?
传统意义上的 javascript 运行在浏览器上, 这是因为浏览器内核实际上分为两个部分, 渲染引擎和 javaScript 引擎. 前者主要负责渲染 HTML+CSS, 后者主要负责运行 javaScript.Chorme 使用的 javascript 引擎是 V8, 它的运行速度非常快.
node 跟 chorme 有什么区别?
架构一样, 都是基于事件驱动的异步架构!
浏览器主要是通过事件驱动来服务页面交互.
node 主要是通过事件驱动来服务 I/O
node 没有 HTML,WebKit 和显卡等等的 UI 技术支持
为什么要用 node.js?
总的来说, Node.js 适合以下场景:
实时性应用, 比如在线多人协作工具, 网页聊天应用等.
以 I/O 为主的高并发应用, 比如为客户端提供 API, 读取数据库.
流式应用, 比如客户端经常上传文件.
前后端分离.
实际上前两者可以归结为一种, 即客户端广泛使用长连接, 虽然并发数较高, 但其中大部分是空闲连接.
Node.js 也有它的局限性, 它并不适合 CPU 密集型的任务, 比如人工智能方面的计算, 视频, 图片的处理等.
当然, 以上缺点不是信口开河, 或者死记硬背, 更不是人云亦云, 需要我们对 Node.js 的原理有一定的了解, 才能做出正确的判断.
基础概念
并发 下面的这些是参考的他人的文章
与客户端不同, 服务端开发者非常关心的一项数据是并发数, 也就是这台服务器最多能支持多少个客户端的并发请求. 早年的 C10K 问题就是讨论如何利用单台服务器支持 10K 并发数. 当然随着软硬件性能的提高, 目前 C10K 已经不再是问题, 我们开始尝试解决 C10M 问题, 即单台服务器如何处理百万级的并发.
在 C10K 提出时, 我们还在使用 Apache 服务器, 它的工作原理是每当有一个网络请求到达, 就 fork 出一个子进程并在子进程中运行 PHP 脚本. 执行完脚本后再把结果发回客户端.
这样可以确保不同进程之间互不干扰, 即使一个进程出问题也不影响整个服务器, 但是缺点也很明显: 进程是一个比较重的概念, 拥有自己的堆和栈, 占用内存较多, 一台服务器能运行的进程数量有上限, 大约也就在几千左右.
虽然 Apache 后来使用了 FastCGI, 但本质上只是一个进程池, 它减少了创建进程的开销, 但无法有效提高并发数.
Java 的 Servlet 使用了线程池, 即每个 Servlet 运行在一个线程上. 线程虽然比进程轻量, 但也是相对的. 有人测试过 , 每个线程独享的栈的大小是 1M, 依然不够高效. 除此以外, 多线程编程会带来各种麻烦, 这一点想必程序员们都深有体会.
如果不使用线程, 还有两种解决方案, 分别是使用协程 (coroutine) 和非阻塞 I/O. 协程比线程更加轻量, 多个协程可以运行在同一个线程中, 并由程序员自己负责调度, 这种技术在 Go 语言中被广泛使用. 而非阻塞 I/O 则被 Node.js 用来处理高并发的场景.
非阻塞 I/O
这里所说的 I/O 可以分为两种: 网络 I/O 和文件 I/O, 实际上两者高度类似. I/O 可以分为两个步骤, 首先把文件 (网络) 中的内容拷贝到缓冲区, 这个缓冲区位于操作系统独占的内存区域中. 随后再把缓冲区中的内容拷贝到用户程序的内存区域中.
对于阻塞 I/O 来说, 从发起读请求, 到缓冲区就绪, 再到用户进程获取数据, 这两个步骤都是阻塞的.
非阻塞 I/O 实际上是向内核轮询, 缓冲区是否就绪, 如果没有则继续执行其他操作. 当缓冲区就绪时, 讲缓冲区内容拷贝到用户进程, 这一步实际上还是阻塞的.
I/O 多路复用技术是指利用单个线程处理多个网络 I/O, 我们常说的 select , epoll 就是用来轮询所有 socket 的函数. 比如 Apache 采用了前者, 而 Nginx 和 Node.js 使用了后者, 区别在于后者效率更高. 由于 I/O 多路复用实际上还是单线程的轮询, 因此它也是一种非阻塞 I/O 的方案.
异步 I/O 是最理想的 I/O 模型, 然而可惜的是真正的异步 I/O 并不存在. Linux 上的 AIO 通过信号和回调来传递数据, 但是存在缺陷. 现有的 libeio 以及 Windows 上的 IOCP, 本质上都是利用线程池与阻塞 I/O 来模拟异步 I/O.
Node.js 线程模型
很多文章都提到 Node.js 是单线程的, 然而这样的说法并不严谨, 甚至可以说很不负责, 因为我们至少会想到以下几个问题:
Node.js 在一个线程中如何处理并发请求?
Node.js 在一个线程中如何进行文件的异步 I/O?
Node.js 如何重复利用服务器上的多个 CPU 的处理能力?
网络 I/O
Node.js 确实可以在单线程中处理大量的并发请求, 但这需要一定的编程技巧. 总之, 在利用 Node.js 编程时, 任何耗时操作一定要使用异步来完成, 避免阻塞当前函数. 因为你在为客户端提供服务, 而所有代码总是单线程, 顺序执行.
文件 I/O
我在之前的文章中也强调过, 异步是为了优化体验, 避免卡顿. 而真正节省处理时间, 利用 CPU 多核性能, 还是要靠多线程并行处理.
实际上 Node.js 在底层维护了一个线程池. 之前在基础概念部分也提到过, 不存在真正的异步文件 I/O, 通常是通过线程池来模拟. 线程池中默认有四个线程, 用来进行文件 I/O.
需要注意的是, 我们无法直接操作底层的线程池, 实际上也不需要关心它们的存在. 线程池的作用仅仅是完成 I/O 操作, 而非用来执行 CPU 密集型的操作, 比如图像, 视频处理, 大规模计算等.
如果有少量 CPU 密集型的任务需要处理, 我们可以启动多个 Node.js 进程并利用 IPC 机制进行进程间通讯, 或者调用外部的 C++/Java 程序. 如果有大量 CPU 密集型任务, 那只能说明选择 Node.js 是一个错误的决定.
事件循环
在 Node.js 中存在一个事件循环(Event Loop).
一次完整的 Event Loop 也可以分为多个阶段(phase), 依次是 poll,check,close callbacks,timers,I/O callbacks ,Idle.
由于 Node.js 是事件驱动的, 每个事件的回调函数会被注册到 Event Loop 的不同阶段. 比如 fs.readFile 的回调函数被添加到 I/O callbacks, setImmediate 的回调被添加到下一次 Loop 的 poll 阶段结束后, process.nextTick() 的回调被添加到当前 phase 结束后, 下一个 phase 开始前.
不同异步方法的回调会在不同的 phase 被执行, 掌握这一点很重要, 否则就会因为调用顺序问题产生逻辑错误.
Event Loop 不断的循环, 每一个阶段内都会同步执行所有在该阶段注册的回调函数. 这也正是为什么我在网络 I/O 部分提到, 不要在回调函数中调用阻塞方法, 总是用异步的思想来进行耗时操作. 一个耗时太久的回调函数可能会让 Event Loop 卡在某个阶段很久, 新来的网络请求就无法被及时响应.
由于本文的目的是对 Node.js 有一个初步的, 全面的认识. 就不详细介绍 Event Loop 的每个阶段了.
数据流
使用数据流的好处很明显, 生活中也有真实写照. 举个例子, 老师布置了暑假作业, 如果学生每天都做一点(作业流), 就可以比较轻松的完成任务. 如果积压在一起, 到了最后一天, 面对堆成小山的作业本, 就会感到力不从心.
Server 开发也是这样, 假设用户上传 1G 文件, 或者读取本地 1G 的文件. 如果没有数据流的概念, 我们需要开辟 1G 大小的缓冲区, 然后在缓冲区满后一次性集中处理.
如果是采用数据流的方式, 我们可以定义很小的一块缓冲区, 比如大小是 1Mb. 当缓冲区满后就执行回调函数, 对这一小块数据进行处理, 从而避免出现积压.
实际上 request 和 fs 模块的文件读取都是一个可读数据流:
总结
对于高并发的长连接, 事件驱动模型比线程轻量得多, 多个 Node.js 进程配合负载均衡可以方便的进行拓展. 因此 Node.js 非常适合为 I/O 密集型应用提供服务. 但这种方式的缺陷就是不擅长处理 CPU 密集型任务.
本来这次是想自己写一篇文章看看的, 但是看到他人写的文章太好了~~~
对不起, 这次的文章后半部分都是参考的他人的文章
来源: https://www.cnblogs.com/z937741304/p/9471904.html