当前位置: 首页 > 后端技术 > Node.js

【本书精读】《深入浅出Node.js》精读笔记分享

时间:2023-04-03 21:17:57 Node.js

写在上一本书介绍中:本书以第一章Node介绍为索引,涉及Node的方方面面。主要内容包括模块机制的揭露和异步I/ODemonstration的实现原理,异步编程的讨论,内存控制的介绍,二进制数据Buffer的详解,Node中的网络编程基础,Node中的web开发,message通过Node.js构建产品所需的流程之间传递、Node测试和预防措施。我的简评:这是一本不可多得的好书,理论和实践结合得很好。如果你是一个纯前端开发者,可以阅读本书来开阔眼界。如果你是全栈开发者,这本书作为入门和深入后端也是非常不错的。推荐阅读。!!文末有pdf书籍,笔记思维导图,以及随书代码打包下载地址。需要的请自取!阅读【书籍精读系列】全部文章,请移步:推荐收藏-JavaScript书籍精读系列导航第1章Node1.1简介。Node的诞生2009年3月,RyanDahl1.2。Node别名Nodejs、NodeJS、Node.js的命名及由来找到了设计高性能Web服务器的几个关键点:事件驱动、非阻塞I/OJavaScript高性能、事件驱动、无历史包袱构建网络应用基础框架1.3.Node带来JavaScript的到来的意义浏览器中除了V8作为JavaScript引擎外,还有WebKit布局引擎。浏览器通过事件驱动服务于界面上的交互,Node通过事件驱动服务于I/O。1.4.Node的特点是异步I/OO。事件和回调函数,单线程,跨平台单线程:弱点1:无法使用多核CPU;弱点2:出错会导致整个应用程序退出,应用程序的健壮性值得检验;弱点3:大量计算占用CPU,无法继续调用异步I/O;Node采用了和WebWorkers一样的思路来解决单线程计算量大的问题:child_process;跨平台:在操作系统和Node上层模块系统之间构建一个平台层架构,即libuv;Node的第三方C++模块也可以使用libuv实现跨平台;1.5.Node的应用场景I/O密集型:面向网络,擅长并行I/O,是否不擅长CPU密集型业务:使用多线程计算;通过编写C++扩展更有效地使用CPU;与遗留系统和平共处分布式应用程序:数据平台,数据库集群1.6。Node用户的前后端编程语言环境统一Node带来的高性能I/O使用并行I/O的实时应用使得用户可以在分布式环境中更高效的使用并行I/O,并有效地使用稳定的接口来提高Web渲染能力。云计算平台为游戏开发领域的工具类应用提供Node支持。第二章模块机制大致经历了工具库、组件库、前端框架、前端应用的变化2.1。CommonJS规范CommonJS的出发点:规范薄弱,存在以下缺陷(无模块系统、标准库少、无标准接口、缺少包管理系统)CommonJS模块规范:主要分为三个部分:模块参考、模块定义和模块识别对象路径分析及文件位置:Node会按照.js、.json、.node的顺序完成扩展名模块编译:每个编译成功的模块都会将其文件路径作为索引缓存在Module._cache对象上;在此过程中,Node将获取到的JavaScript文件的内容进行包装;(function(exports,require,module,__filename,__dirname){n,n加在最后});C/C++模块,Node调用process.dlopen()方法加载执行;2.3.CoremoduleJavaScript核心模块编译过程:C/C++文件放在Node项目的src目录下,JavaScript文件存放在lib目录下;编译器需要编译的所有JavaScript模块文件都是C/C++代码;C/C++核心模块编译过程:Node启动时会生成一个全局变量process,并提供Binding()方法辅助内置模块2.4。C/C++扩展模块说明:一个JavaScript的典型弱点是位操作;*nix下需要通过g++/gcc等编译器编译成动态链接共享目标文件.so,Windows下需要通过VisualC++编译器编译成动态链接库文件.dll;前提条件:GYP工程生成工具、V8引擎C++库、libuv库、Node内部库等C/C++扩展模块编写:普通扩展模块和内置模块的区别是不需要编译源码进入Node,而是通过dlopen()方法编译动态加载的C/C++扩展模块:写一个.gyp项目文件是编码之外的重中之重;编译过程会根据平台分别通过make或vcbuild进行编译;C/C++扩展模块的加载:require()方法解析标识符,路径分析,文件位置,然后加载执行;加载.node文件其实要经过两步,第一步是使用uv_dlopen方法打开动态链接库,第二步是调用uv_dlsym()方法找到NODE_MODULE宏定义的方法地址在动态链接库中;2.5.模块调用栈JavaScript核心模块主要担当两类职责:一是对C/C++内置模块的封装层和桥接层,供文件模块调用;一种是纯功能模块,不需要和底层打交道2.6.PackageandNPMPackagedescriptionfileandNPM:packagespecification的定义可以帮助Node解决依赖包安装问题;npm实际需要的字段主要有name、version、description、keywords、repositories、author、bin、main、scripts、engines、dependencies、devDependencies;NPM常用功能:NPM帮助完成第三方模块的发布、安装和依赖;查看帮助npm,分析包npmls;本地NPM:可以在NPM上享受很多包,同时对自己的包保密,限制NPM潜在问题:开发者水平不同,包质量也有好有坏;npm模块主页上的依赖列表可以说明模块的质量和可靠性;GitHub上项目的观察者数量和分支数量从侧面反映了模块的可靠性和流行度;计划在CPAN社区引入Kwalitee风格,让Modules自然排序;2.7.前后端共享模块模块重点:前端通过网络加载代码,瓶颈在带宽,后端从磁盘加载,瓶颈在资源等作为CPU和内存。AMD规范:AMD模块需要通过defineAmodule明确定义,但是在Node实现中是隐式封装的CMD规范:CMD和AMD规范的主要区别在于定义模块和依赖import的部分兼容多模块规范:封装兼容Node、AMD、CMD和常见浏览器环境异步IO第三章,异步I/O伴随事件驱动和单线程3.1。为什么异步I/O用户体验:I/O是昂贵的,分布式I/O更昂贵资源分配:使用单线程避免多线程死锁和状态同步等问题;使用异步I/O让单线程远离阻塞,更好的利用CPU3.2。异步I/O及现状应用最广泛,但不是Node独创的异步I/O和非阻塞I/O:操作系统内核对I/O只有两种方式:阻塞和非阻塞;现有的轮询技术主要有:read、select、poll、epoll、kequeue(read:反复调用检查I/O状态,完成完整数据的读取;select:通过文件描述符上的事件状态判断;poll:使用链表可以避免数组的长度限制,其次可以避免不必要的检查;epoll:Linux下最高效的I/O事件通知机制;kqueue:类似于epoll,但只存在于FreeBSD系统下)理想non-blockingasynchronousI/O现实的异步I/O:在Node中,无论是*nix还是Windows平台,内部都有另一条线完成I/O任务程迟3.3.Node的异步I/O事件循环:Node自身的执行模型:事件循环观察者:在Node中,事件主要来自于网络请求、文件I/O等;在Windows下,这个循环是基于IOCP创建的,而在*nix下,它是基于多线程创建的;request对象:存在于JavaScript发起调用到内核执行I/O操作的过渡过程中的中间产物执行回调:事件循环、观察者、request对象、I/O线程池这四者共同构成了request的基本要素节点异步I/O模型;完成异步I/O的过程(Windows下通过IOCP向系统内核发送I/O调用,从内核获取完成的I/O操作,带有事件循环;Linux下通过epoll实现;FreeBSD下通过kqueue实现;Solaris下通过Eventports实现),setImmedate(),process.nextTick()定时器:实现原理类似异步I/O,但不需要I/O线程池的参与;问题是它不准确;process.nextTick():定时器的使用需要使用红黑树创建定时器对象并进行迭代操作;定时器中使用红黑树的操作时间复杂度为O(lg(n)),nextTick()的时间复杂度为O(1);setImmediate():process.nextTick()中回调函数的执行优先级高于setImmediate();process.nextTick()属于idle观察者,setImmediate()属于check观察者;3.5.事件驱动和高性能的服务器节点不需要为每个请求创建额外的对应线程而不受线程上下文切换开销的影响。一些著名的事件驱动实现:Ruby的EventMachine;Perl的AnyEvent;Python的扭曲;第四章异步编程平台大规模走向应用层4.1.函数式编程Higher-orderfunctions:Higher-orderfunctionsisfunctionsthatcantakefunctionsasparametersorreturnvalues通过指定一些参数生成新的自定义函数的形式是偏函数;4.2.异步编程的优点和难点优点:Node的异步模型和V8的高性能难点:异常处理,函数数字嵌套,阻塞代码,多线程编程,异步到同步4.3。异步编程方案事件发布/订阅模式:如果一个事件添加的监听器超过10个,会得到一个警告,使用emitter.setMaxListeners(0)解除对Promise/Deferred模式的限制:Deferred主要用于内部维护状态异步模型;对外使用Promise,通过then()方法暴露给外部,增加自定义逻辑;Promise/Deferred模式在业务中不会将可变部分封装在Deferred中,可变部分交给Promise;流程控制库:tailtrigger和Next,目前用的最多的地方就是Connect这个中间件;async,长期占据npm依赖列表前三,series实现异步串行执行,parallel实现异步并行执行,waterfall实现异步调用的依赖处理;脚步,打火机;wind,思路完全不同的异步编程方案;4.4.异步并发控制同步I/O,每个I/O相互阻塞,不会出现文件描述符消耗过多的情况。bagpip的解决方案:bagpipe模块的解决方案(通过一个队列控制并发量;发起但未执行的异步调用量小于Limit值,从队列中取出执行;如果主动调用达到limitvalue,调用暂存在队列中;每次异步调用结束时,从队列中取出新的异步调用执行;);拒绝访问;超时控制;async解决方案:async中的ParallelLimit()用于处理异步调用。Chapter5内存控制基于非阻塞和事件驱动的Node服务,具有低内存消耗的优势,非常适合处理海量网络请求。5.1.V8垃圾回收机制和内存限制Node和V8的内存限制:只能使用部分内存(64位系统下约1.4G,32位系统下约0.7G)V8的对象分配:V8仍然提供选项让我们使用更多--max-old-space-size设置老年代内存空间的最大值;--max-new-space-size设置新生代的内存空间大小;V8的垃圾回收机制:垃圾回收策略主要基于世代垃圾回收机制;在分代的基础上,新生代中的对象主要通过Scavenge算法进行垃圾回收;V8在老年代主要使用Mark-Sweep和Mark-Compact的组合进行垃圾回收;检查垃圾收集日志:启动时添加--trace-gc参数;节点启动使用--prof参数时,可以获取V8执行过程中的性能分析数据;提供用于统计日志信息的linux-tick-processor工具;5.2.高效使用内存作用域:可以形成作用域的函数包括函数调用、with和全局作用域闭包:从外部作用域访问内部作用域中的变量的方法。不能立即回收的内存有闭包和全局变量引用。5.3.内存指标查看内存使用情况:process.memoryUsage()可以看到Node进程的内存使用情况;os模块的totalmem()和freemem()查看系统总内存和空闲内存;堆外内存:没有通过V8分配的内存称为堆外内存;使用堆外内存可以突破5.4的内存限制。内存泄漏内存泄漏有几个原因:缓存和队列消费不及时,作用域没有释放。谨慎使用内存作为缓存:任何试图在Node中使用内存作为缓存的行为都应该受到限制。关注队列Status:让任何异步调用的回调都有一个可控的响应时间5.5。内存泄露排查Node应用内存泄露定位常用工具:v8-profiler、node-headpdump、node-mtrace、dtrace、node-memwatchnode-headdumpnode-memwatch5.6。大内存应用Node提供了一个流模块来处理大文件。注意,即使V8不限制堆内存的大小,物理内存仍然是有限的。第6章理解Buffer6.1。Bufferstructure模块结构:Buffer和Array一样是一个对象,但是主要用来操作对象Buffer对象:buf[10]的元素值是0到255之间的随机值Buffermemoryallocation:内存分配Buffer对象不在V8的堆内存中,而是在Node的C++层面实现内存申请;Node以8KB为界限来区分Buffer是大对象还是小对象;真正的内存在Node的C++层面提供,JavaScript层面只使用它;6.2.Buffer转换目前支持的字符串编码类型:ASCII、UTF-8、UTF-16LE/UCS-2、Base64、Binary、HexstringtoBufferBuffertostring编程类型Buffer不支持:Buffer提供了一个isEncoding()函数来判断是否编码支持转换;iconv和iconv-lite这两个模块可以支持更多的编码类型转换;6.3.缓冲区拼接乱码如何生成setEncode()和string_decoder()来正确拼接Buffer:调用Buffer.concat()方法生成合并后的Buffer对象6.4.Buffer与性能Buffer广泛应用于文件I/O和网络I/OBuffer是二进制的,数据、字符串、Buffer之间存在编码关系。Chapter7NetworkProgrammingNode提供了四个模块:net、dgram、http、https,分别用于处理TCP、UDP、HTTP、HTTPS。7.1.构建TCP服务,创建TCPTCP服务器端TCP服务事件:TCP套接字是可写可读的Stream对象,可以巧妙的用pipe()方法实现7.2.构建UDP服务创建UDP套接字创建UDP服务器创建UDP客户端UDP套接字事件:UDP套接字只是EventEmitter的一个实例,而不是Stream7.3的一个实例。构建HTTP服务HTTPhttp模块:TCP服务以连接为单位,HTTP服务以请求为单位HTTP客户端7.4构建WebSocket服务WebSocket握手WebSocket数据传输7.5.网络服务与安全Node在网络安全方面提供了3个模块,分别是crypto、tls、httpsTLS/SSTLS服务HTTPS服务Chapter8构建Web应用8.1.基本功能请求方法路径解析查询字符串Cookie:cookie处理的几个步骤(服务器将cookie发送给客户端;浏览器保存cookie;之后,浏览器每次都将cookie发送给服务器)Session:两种常见的实现方法(基于Cookies用于映射用户和数据;查询字符串用于映射浏览器端和服务器端数据);Connect默认使用connect_uid,Tomcat使用jsessionid等;caching:提高性能,YSlowCachingrules中提到的几项(给消息添加Expires或Cache-Control;配置ETags;使Ajax可缓存)Basicauthentication:Base64加密后在网络中传输,缺点太多8.2.数据上传表单数据:通过headerTransfer-Encoding或Content-Length可以判断请求中是否有内容附件上传:当浏览器遇到multipart/form-data表时单次提交时,构造的请求报文与普通形式完全不同;强大,基于流处理,解析报文,将接收到的文件写入系统临时文件夹,并返回对应路径;数据上传和安全8.3。路由分析文件路径类型MVCRESTful8.4。中间件异常处理中间件和性能从杂乱的发散状态收敛到非常规则的组织8.5。页面渲染内容响应:不同的文件类型有不同的Mime值;Content-Disposition字段影响的行为是客户端会根据其值判断消息数据是作为即时浏览内容还是可下载附件;视图渲染模板:本质是通过模板引擎HTML代码生成最终的模板文件和数据;形成模板技术的四要素(模板语言;包含模板语言的模板文件;带有动态数据的数据对象;模板引擎);小胡子,弱逻辑模板;最著名的有EJS、Jade等;Bigpipe:用于限制调用流量,解决大数据页面加载速度问题;解决方案是将页面分成多个部分,先将没有数据的布局输出给用户,逐步将每个部分输出到前端,然后渲染填充框架,完成整个网页的渲染;第9章玩转流程9.1。服务模型的变化石器时代:同步——只存在于一些没有并发要求的应用中青铜时代:复制进程——要复制更多的数据,启动相对较慢白银时代:多进程——上下文切换会消耗时间黄金时代:事件驱动——内存消耗问题著名的C10k问题;单线程避免了不必要的内存开销和上下文切换开销;9.2.多进程架构的child_process.fork()副本是一个独立的进程。独立全新的V8实例启动多个进程只是为了充分利用CPU资源,并不是为了解决并发问题和创建子进程之间的进程间通信:JavaScript主线程与UI渲染共享同一个线程;进程间通信的技术有很多,如命名管道、匿名管道、套接字、信号量、共享内存、消息队列、DomainSockets等;操作系统文件描述符是有限的;handles传递:句柄是一个引用,可以用来标识一个资源,它包含一个指向对象的文件描述符;文件描述符实际上是一个整数值;Node进程之间只有消息传递,没有真正的对象传递;9.3.集群稳定之路进程事件自动重启:先创建新的worker进程,负载均衡后退出异常进程:round-robin调度的工作方式是master进程接受连接,依次分发给worker进程。状态共享:最简单直接的数据共享解决方案方法是通过第三方存储数据;主动通知;9.4.Cluster模块Cluster工作原理:其实就是child_process和net模块的组合ApplicationClustereventChapter10Test10.1。单元测试编写可测试代码的几个原则:单一职责、接口抽象、层分离;单元测试主要包括断言、测试框架、测试用例、测试覆盖率、mock、持续继承等,由于Node的特殊性,也会加入异步代码测试和私有方法测试;JavaScript的断言规范最早来源于CommonJS单元测试规范;单元测试风格主要有TDD(测试驱动开发)和BDD(行为驱动开发);BDD主要使用describe和it来组织测试用例;TDD使用testcase组织主要由suite和test完成;单元测试覆盖率方便我们定位未测试的代码行;私有方法测试(Java等语言,可以通过反射实现私有方法访问;巧妙利用闭包的诀窍,在执行eval()时,实现对模块内部局部变量的访问,从而实现局部变量可以导出到测试用例调用执行;)10.2。性能测试单元测试主要用来检测代码的行为是否符合预期,性能测试的范围比较广,包括负载测试、压力测试和基准测试。基准测试。压力测试:最常用的工具有ab、siege、http_load等。基准测试驱动测试数据和业务数据的开发和转换。第11章产品化11.1。构建目录结构的项目工程工具:在Web应用中,通常将一些构建任务写在Makefile中,以帮助提高效率;合并编译、应用打包、运行测试、清理目录、扫码等;编码标准:一种是基于文档的公约,一种是提交代码时强制检查代码审查11.2。部署过程部署环境部署操作11.3.Performance几个拆分原则:做具体的事情;让好工具做好事;简化模型;风险分离;动静分离启动缓存多进程架构读写分离:数据库的读写分离,将数据库设计为主从11.4。日志访问日志异常日志:log和info方法都向标准输出process.stdout输出信息,warn和error方法向标准错误process.stderr输出信息;console对象上有一个Console属性,它是console对象的构造函数;回调函数中产生的异常被全局的uncaughtException事件捕获;日志和数据库拆分日志11.5、监控和告警监控:一个是业务逻辑监控,一个是硬件监控;主要指标:日志监控、响应时间、进程监控、磁盘监控、内存监控、CPU使用率监控、CPU负载监控、I/O负载、网络监控、应用状态监控、DNS监控;实现报警监控系统稳定11.6.典型的稳定性横向扩展方法是多进程、多机、多机房写在后面的pdf书,注意思维导图,带书码的打包下载地址:https://pan.baidu.com/s/1OhLjjtfffjX3hv2_Pw7AHQ(提取码:9m33)纸质书京东购买地址:https://u.jd.com/wHmeh4(建议购买纸质书学习)