node项目架构与优化
为什么要用node
1、前后端耦合太紧密,中间加一层node,还要给前端装一些乱七八糟的东西 java啥的服务环境。还有后台返回接口的时候不管前端需不需要那些接口一起返回,其实只用到1~2 条数据。本来ajax就非常消耗时的事,就用其中一条,用node做一层中间层处理把没用的接口剔除掉。
2、比如:一个10天项目,后台开发6天,前台2天,测试2天。后台开发时,没有接口,前端得等着后台的接口,如果后台某种情况推迟了一天,前端就1天开发,测试要2天,肯定前端开发不完。使用node我们可以并行开发,提高开发效率。
3、proxy 代理层 java->node 前端->node 这种情况叫BFF,后台是没有跨域的。做了一层中间代理,可以自己控制路由,前端请求发给node,就不会出现跨域的问题。node socket io 实时通讯。
4、node容错,性能难做。
node异步IO原理浅析及优化方案
-
异步IO的是与非
前端通过异步IO可以消除UI阻塞。
假设请求资源A的时间为M,请求资源B的时间为N,那么同步的请求耗时就为M+N,如果采用异步的方式占用时间为MAX(M,N)。
随着业务的复杂,会引入分布式系统,时间会线性的增加M+N+...和MAX(M,N,...),这会放大同步和异步之间的差异。
I/O是昂贵的,分布式I/O是更昂贵的。
一些底层知识
1、
CPU是钟周期:1/CPU主频->1s/3.1GHz
一级CPU是钟周期:3.5GHz 1/3.5*3 io时间
2、
p是并行系统钟的处理器数量;
f=ws/w为串行部分的比例;
我们分布到其他机器里,每个机器都用同样的代码。网络要请求这文件,就比较慢 整个时间就比较长。
并不是所有的所有并行的好,也并不少所有串行就好,分情况。系统会自动根据情况来判断使用并行还是串行。
3、操作系统对计算机进行来抽象,将所有输入输出设备抽象为文件。内核在进行文件I/O操作时,通过文件描述符进行管理。应用程序如果需要进行I/O需要打开文件描述符,在进行文件和数据的读写。异步IO不带数据直接返回,要获取数据还需要通过文件描述符再次读取。
NodeJS使用与IO密集型不适用于CPU密集型
比如银行,每次计算价格,java是多线程可以处理多个任务,而Nodejs是单线程用异步不停的写,写发上也非常复杂。可以有框架处理单线程问题,但是性能上还是不如java。
-
Node对异步IO实现
完美的异步IO应该是应用程序发起阻塞调用,无需通过便利或者事件幻想等方式轮询。
前端的event loop跟node event loop不一样,前端比node event loop还要复杂一些。node程序进来不停的转,就等于我们把米放到电饭煲里,定好时间,我们去忙别的,等他做好之后,会提示,已经做好了。
-
几个特殊对API
1、setTimeout和setInterval线程池不参与
2、process.nextTick()实现类似setTimeout(function(){},0);每次调用放入队列中,在下一轮循环中取出。
3、setImmediate();比provess.nextTick()优先级低
4、Node如何实现一个Sleep?
async function test(){ console.log('hello'); await seelp(1000); console.log('word'); } function seelp(time){ return new Promise(resolve=>setTimeout(resolve,time)) } test()
-
函数式编程在Node中对应用
1、高阶函数:可以将函数作为输入或者返回值,形成一种后续传递风格的结果接受方式,而非单一的返回值形成。后续传递风格的程序将函数业务重点从返回值传递到回调函数中。
app.use(function(){ //todo }) var emitter = new event.eventEmitter; emitter.on(function(){ //todo })
2、偏函数:指定部分参数产生一个新的定制函数的形式就是偏函数。node中异步编程非常常见,我们通过哨兵变量会很容易造成业务的混乱。underscore,after变量。
-
常用的Node控制异步API的技术手段
1、step、wind(提供等待的异步库)、bigpipe、Q.js
2、Async、Await
3、Promise/Defferred是一种先执行异步调用,延迟传递的处理方式。Promise是高级接口,事件是低级接口。低级接口可以构建更多复杂的场景,高级接口一旦定义,不太容易变化,不再有低级接口的灵活性,但对于解决问题非常有效。
4、由于Node基于V8的原因,目前还不支持协程。协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。
一个程序可以包含多个协程,可以对比一个进程包含多个线程。来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程有当前协程来控制。
内存管理与优化
V8垃圾回收机制
node使用javascript在服务端操作大内存对象受到了一定的限制,64位系统下约为1.4GB,32位系统是0.7GB
process.memoryUsage->rss、heaptTotal、heapUsed
V8的垃圾回收策略主要基于分代式垃圾回收机制。在自动垃圾回收的演变过程中,人们发现没有一种垃圾回收算法能够胜任所有场景。V8内存分为新生代和老生代。新生代为存活时间较短对象,老圣代为存活时间较长的对象。
Scavenge算法
在分带基础上,新生代的对象主要通过Scavenge算法进行垃圾回收,在具体实现时主要采用cheney算法,cheney算法是一种采用复制的方法实现的垃圾回收算法。它将内存一分为二,每一个空间称为semispace。这两个semispace中一个处于使用,一个处于闲置。处于使用的称之为From,检查From存活对象复制到To。非存活被释放。然后互换位置。再次进行回收,发现被回收过直接晋升,或者发现To空间已经使用来超过25%。他的缺点只能使用堆内存的一半,这是一个典型的空间换时间的办法,但是新生代生命周期较短,恰恰就适合这个算法。
老生代空间生成完成肯定要有东西管理它,就有来mark-sweep和mark-compact
V8老生代主要采用mark-sweep和mark-compact,在使用Scavenge不合适。一个是对象较多需要赋值量太大而且还是没能解决空间问题。Mark-Sweep是标记清除,标记那些死亡的对象,然后清除。但是清除过后出现内存不连续的情况,所有我们要使用Mark-Compact,他是基于mark-sweep演变而来的,它现将活着的对象移到一边,移动完成后,直接清理边界外的内存。当CPU空间不足的时候会非常高效。V8还引入来延迟处理,增量处理,并计划引入并标记处理。
常见的内存泄漏问题
1、无限制增长的数组
2、无限制设置属性和值
3、任何模块内的私有变量和方法均是永驻内存的a=null
4、大循环,无GC(垃圾回收机制)机会
内存泄漏分析
node-inspector
console.log("Server PID",process.pid);
sodu node --inspect app.js
whie true;do curl "http:localhost:1337/";done
top -pid 2322
大规模Node站点结构原理分析
经典的MVC框架
model-view-Controller
javaweb多层架构
node 集群应用
预备上线
1、前端工程化的搭载动态文件的MAP分析压缩打包合并至CDN
2、单侧、压力测试、性能分析工具发现Bug
3、编写nginx-conf实现负载均衡和反向代理
4、PM2启动应用生序小流量灰度上线,修复Bug
5、上线前的不眠夜、你见过凌晨5点的北京么?
多线程
1、master进程均为主进程,Fork可以创造主进程。
2、通过child_process可以和NET模块组合,可以创建多个线程并监听统一端口。通过句柄传递完成自动启动、发射自杀信号、限量重启、负载均衡。
3、node默认的机制是采用操作系统的抢占式策略。闲着的进程争抢任务,但是会造成CPU闲置的IO暂时并未闲置。Node后来引入来Round-Robin机制,也叫轮询调度。主进程接受任务,在发。
4、每个子进程做好自己的事,然后通过进程间通信来将他们链接起来。这符合Unix的设计理念,每个进程只做一件事,并做好。将福啊分解为简单,将简单组合程强大。
PM2
pm2是一个自带负载均衡功能的node应用的进程管理器。
当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远活着,0秒重载。
1、内建负载均衡(使用node cluster集群模块)
2、后台运行
3、0秒停机重仔
4、具有Ubuntu和CentOS的启动脚本
5、停止不稳定的进程(避免无限循环)
6、控制台检测
7、提供HTTP API
8、远程控制和实时的接口API(Nodejs 模块,允许和PM2进程管理交互)
测试过Nodejs v0.11 v0.10 v0.8版本,兼容CoffeeScript,基于linux和MacOs
我之前是前端连java感觉还可以,如果我中间加一层node,是不是就变慢了呢,并不是,首先java跟node在同样一个机房他俩请求的时间被严格限制在1ms(毫秒),甚至一些团队100ms已经顶天了,300ms服务肯定有问题。用户肯定无法感知的,node和java走了一次http,会有3次握手,即使是毫秒也是一次浪费,node和java 走的是Linux Virtual Server(Linux虚拟服务器)通信。