一、Node简介
二、模块机制
A.CommonJS规范
1.模块引用:通过require()方法来引入外部模块
2.模块定义:提供exports对象用于导出当前模块的方法或者变量,并且是唯一导出的出口
3.模块标识:就是传递给require()方法的参数,必须是符合小驼峰命名的字符串,或者以.、..开头的相对路径
B.Node的模块实现
1.不论是核心模块还是文件模块,require()方法对相同模块的干净加载都一律采用缓存优先的方式,这是第一优先级的
2.核心模块》路径形式的文件模块》自定义模块(自定义模块的生成方式与JS原型链或作用域链的查找方式十分类似)
3.Node会按.js、.json、.node次序补足扩展名,在尝试的过程中,需要调用fs模块同步阻塞式地判断文件是否存在,这里会是一个引起性能问题的地方,如果是.node和.json文件,在传递给require()时带上扩展名
4.js模块的编译:包装成(function(exports, require,module,__filename,__dirname)){….})的方式
C.核心模块
1.JS核心模块
2.C/C++核心模块
D.C/C++扩展模块
1.JS的一个典型弱点是位运算,效率不高
E.模块调用栈
1.C/C++内建模块属于最底层模块,如果不是非常了解要调用的C/C++内建模块,尽量避免使用process.binding()方法直接调用
2.JS核心模块的职责:作为C/C++内建模块的封装层和桥接层;纯粹的功能模块;
3.文件模块通常由第三方编写,包括普通JS模块和C/C++扩展模块
F.包与NPM
1.包描述文件:package.json,可以帮助Node解决依赖包安装的问题
G.前后端共用模块
1.AMD、CMD规范
三、异步I/O
A.为什么要异步I/O
1.用户体验
2.资源分配
B.异步I/O实现现状
1.阻塞/非阻塞:操作系统内核对于I/O只有两种方式,阻塞与非阻塞
2.理想的非阻塞异步I/O:AIO(仅支持Linux,仅支持内核I/O中的0_DIRECT方式读取,无法利用系统缓存)
3.现实的异步I/O:模拟线程池、glibc的AIO、libeio、windows下的IOCP
C.Node的异步I/O
1.事件循环:Node自身的执行模型,在进程启动时,Node便会创建一个类似于while(true)的循环,每执行一次循环体的过程我们称为Tick,每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行它们
2.观察者:每个事件循环中有一个或者多个观察者,而判断是否有事件要处理的过程就是向这些观察者询问是否有要处理的事件,浏览器采用了类似的机制,Node中有文件I/O观察者、网络I/O观察者等
3.事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求等则是事件的生产者,事件被传递到观察者那里,事件循环则从观察都那里取出事件并处理
4.请求对象:从JS发起调用到内核执行完I/O操作的过渡过程中,存在一种中间产物,叫做请求对象
5.事件循环、观察者、请求对象、I/O线程池这四者共同构成了Node异步I/O模型的基本要素
D.非I/O的异步API
1.定时器
2.process.nextTick()
3.setImmediate()
E.事件驱动与高性能服务器
1.Node通过事件驱动 的方式处理请求,无须为每一个请求创建额外的对应线程,可以省掉创建线程和销毁线程的开销,同时操作系统在调度任务时因为线程少,上下文切换的代价很低
2.Nginx同样采用事件驱动的方式
四、异步编程
A.函数式编程
1.高阶函数:可以将函数作为参数或是返回值,并形成了一种后续传递风格,将函数的业务重点从返回值转移到了回调函数中
2.偏函数:是指创建一个调用另外一个部分——参数或变量已经预置的函数——的函数用法。通过指定部分参数来产生一个新的定制函数的形式就是偏函数
B.异步编程的优势与难点
1.优势
2.难点
C.异步解决方案
1.事件发布/订阅模式
2.Promise/Deferred模式
3.流程控制库
D.异步并发控制
1.异步I/O与同步I/O的显著差距:同步I/O因为每个I/O都是彼此阻塞的,在循环体中,总是一个接一个调用,不会出现耗用文件描述符太多的情况,同时性能也是低下的;对于异步I/O,虽然并发容易实现,但是由于太容易实现,依然需要控制。尽管是要压榨底层系统的恒通,但还是需要给予一定的过载保护,以防止过犹不及
2.bagpipe的解决方案
3.async的解决方案:parallelLimit()方法
五、内存控制
A.V8的垃圾回收机制与内存限制
1.V8的内存限制:64位系统下约为1.4GB,32位系统下约为0.7GB
2.V8中,所有的JS对象都是通过堆来进行分配的,使用process.memoryUsage()来查看,heapTotal和heapUsed表示已申请到的内存和当前使用的量,rss是resident set size的缩写,即进程的常驻内存部分
3.在V8中,主要将内存分为新生代和老生代,新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象
4.在分代的基础上,新生代中的对象主要通过Scavenge算法进行垃圾回收。在Scavenge的具体实现中,主要采用了Cheney算法;在老生代中主要采用了Mark-Sweep和Mark-Compact相结合的方式进行垃圾回收
5.为了降低全堆垃圾回收带来的停顿时间,V8先从标记阶段入手,将原本要一口气停顿完成的动作改为增量标记(incremental marking),也就是拆分为这么多小“步进”,每做完一“步进”就让JS应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成
B.高效使用内存
1.作用域:如果变量是全局变量(不通过var或定义在global变量上),由于全局作用域需要直到进程退出才能释放,此时将导致引用 的对象常驻内存(常驻在老生代中),如果需要释放常驻内存的对象,可以通过delete操作来删除引用关系,在V8中通过delete删除对象的属性有可能干扰V8的优化,所以通过赋值方式解除引用更好
2.闭包:一旦有变量引用中间函数,这个中间函数将不会释放,同时也支使原始的作用域不会得到释放,作用域中产生的内存占用也不会得到释放。除非不同有引用,都会逐步释放
C.内存指标
1.查看内存使用情况
2.os模块中的totalmem()和freemen()这两个方法用于查看操作系统的内存使用情况,分别返回系统的总内存和闲置内存
不是通过V8分配的内存称为堆外内存,利用堆外内存可以突破内存限制 的问题
3.Node的内存构成主要由通过V8进行分配的部分和Node自行分配的部分。受V8的垃圾回收限制的主要是V8的堆内存
D.内存泄漏
1.在Node中,缓存并非物美价廉,一旦一个对象被当做缓存来使用,那就意味着它将会常驻在老生代中。缓存中的键越多,长期存活的对象也就越多,这将导致垃圾回收在进行扫描和整理时,对这些对象做无用功
2.尽量使用外部缓存,如Redis和Memcached
3.队列问题,如数据库写入操作的堆积:
E.内存泄漏排查
1.node-heapdump、node-memwatch等工具
F.大内存应用
1.Node提供了stream处理大文件,如果不需要进行字符串层面的操作,则不需要V8来处理,可以尝试进行纯粹的Buffer操作,这不会受到V8内存堆的限制
六、理解Buffer
A.Buffer结构
1.Buffer是一个典型的JS与C++结合的模块,它将性能相关部分用C++实现,将非性能相关的部分用JS实现
2.Buffer受Array类型的影响很大,可以访问length属性得到长度,也可以通过下标访问元素;给元素的赋值如果小于0,就将该值逐次加到256,直到得到一个0到255之间的整数。如果得到的数值大于255,就逐次减256,如果是小数,舍弃小数部分
3.Node在内存的使用上应用的是在C++层面申请内存、在JS中分配内存的策略。Node采用了slab分配机制
B.Buffer的转换
1.字符串:
C.Buffer的拼接
1.Buffer不等于字符串,只是会隐式转换!!需要注意编码问题
2.setEncoding()只能处理utf8、Base64和UCS-2/UTF-16LE这3种编码
3.用一个数组来存储接收到的所有Buffer片段并记录下所有片段的总长度,然后调用Buffer.concat()方法生成一个合并的Buffer对象。Buffer.concat()方法封装了从小Buffer对象向大Buffer对象的复制过程。
D.Buffer与性能
1.通过预告转换静态内容为Buffer对象,可以有效地减少CPU的重复使用,节省服务器资源。在Node构建的Web应用中,可以选择将页面中的动态内容和静态内容分离,静态内容部分可以通过预先转换为Buffer的方式,使性能得到提升。由于文件自身是二进制数据,所以在不需要改变内容的场景下,尽量只读取Buffer,然后直接输出,不做额外的转换,避免损耗
2.highWaterMark的大小对性能的影响
3.如果文件较小(小于8kb),有可能造成slab未能完全使用;对于大文件而言,highWaterMark的大小决定会触发系统调用和data事件的次数;读取一个相同的大文件时,highWaterMark值的大小与速度的关系:该值越大,读取速度越快
七、网络编程
A.构建TCP服务
1.服务器事件(net.createServer()):listening、connection、close、error
2.连接事件(net.connecct()):data、end、connect、drain、error、close、timeout
3.在Node中,由于TCP默认启用了Nagle算法
B.构建UDP服务
1.UDP事件:message、listening、close、error
C.构建HTTP服务
1.http服务端事件:connection、request、close、checkContinue、connect、upgrade、clientError
2.http客户端事件:response、socket、connect、upgrade、continue
D.构建WebSocket服务
E.网络服务与安全
1.Node在网络安全上提供了3个模块,分别为crypto、tls和https
八、构建Web应用
1.Cookie优化:减小Cookie的大小;为静态组件使用不同的域名;减少DNS的查询;
2.缓存规则:添加Expires或Cache-Control到报文头中;配置ETags;让Ajax可缓存;
3.清除缓存:url请求后带版本号,如http://xxx.com/?v=1.0.0
4.Content-Disposition,inline表示内容只需即时查看,attachment表示数据可以存为附件
九、玩转进程
1.PHP的健壮性是由它给每个请求都建立独立的上下文来实现的
2.Master-Worker模式,又称主从模式。主进程不负责具体的业务处理,而是负责调度或管理工作进程,它是趋向于稳定的。工作进程负责具体的业务处理。
3.child_process模块:
4.WebWorker允许创建工作线程并在后台运行,使得一些阻塞较为严重的计算不影响主线程上的UI渲染
5.IPC(Inter-Process Communication,进程间通信),是为了让不同的进程能够互相访问资源并进行协调工作,Node中使用的是管道(pipe)技术
6.句柄是一种可以用来标识资源的引用,它的内部包含了指向对象的文件描述符
7.Cluster事件:fork、online、listening、disconnect、exit、setup
十、测试
A.单元测试
1.编写可测试代码的原则:单一职责、接口抽象、层次分离
2.单元测试主要包含断言、测试框架、测试用例、测试覆盖率、mock、持续集成等,Node还会加入异步代码测试和私有方法测试
3.断言:是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示程序开发者预期的结果——当程序运行到断言的位置时,对应的断言应该为真。若断言不为真,程序会中止运行,并出现错误信息
4.Node中的assert模块包含:ok()、equal()、notEqual()、deepEqual()、notDeepEqual()、strictEqual()、notStrictEqual()、throws()、doesNotThrow()、ifError()
5.单元测试测试风格:
6.相关工具:mocha、blanket、jscover、muk、Makefile、travis-ci
B.性能测试
1.基准测试:benchmark
2.压力测试:ab、siege、http_load
十一、产品化
A.项目工程化
1.目录结构 :只要遵循单一原则即可
2.构建工具:Makefile、Grunt
3.编码规范:JSLint、JSHint
4.代码审查
B.部署流程
1.在实际的项目需求中,有两点需要验证:一是功能的正确性,一是与数据相关的检查
C.性能
1.拆分原则:做专一的事、让擅长的工具做擅长的事情、将模型简化、将风险分离
2.动静分离、启用缓存、多进程架构、读写分离
D.日志
1.访问日志、异常日志、数据库记录、分割日志
E.监控报警
1.监控:日志监控、响应时间、进程监控、磁盘监控、内存监控、CPU占用监控、CPU load监控、I/O负载、网络监控、应用状态监控、DNS监控
2.报警的实现:邮件报警、短信或电话报警
F.稳定性
1.多机器:需要考虑负载均衡、状态共享、数据一致性、反向代理
2.多机房
3.容灾备份
G.异构共存
1.通过协议与已有的系统进行异构共存
附录B.调试Node
1.Debugger
通过debugger;设置断点
使用node debug xxxx.js
步进指令:cont或c、next或n、step或s、out或o、pause
2.Node Inspector
https://github.com/zhangyue0503/html5js/tree/master/shenruqianchunodejs