node学习日志(一)--Node.js设计核心理念
最近看《node即学即用》,做点笔记~~
Nodejs设计的核心理念:
1.事件循环:
要点一:所有I/O事件都是非阻塞的
要点二:用回调函数来处理I/O,回调函数以级联的方式嵌在其他回调函数中
要点三:事件驱动是以“单线程”的方式运行,即同一时间只能处理一件事情
例子:web服务器要求从数据库读取一些数据,然后返回给用户
1.web服务器请求-->回调函数A【从请求对象中得知要从数据库读些什么】
2.回调函数A-->数据库【发起具体请求,并传入回调函数B】
3.回调函数A结束并返回
4.数据库【找到所需要的内容,触发相应事件】-->回调函数B【由事件循环队列调用】
5.回调函数B【将数据发给用户】
不适用node:服务器大部分工作是计算
编写node服务器策略:
1.设置完成后,所有操作都是事件驱动的
2.如果Node.js需要长时间处理数据,就需要考虑把它分配给web worker去处理
**事件驱动方法配合事件循环工作起来非常高效-->但是如果所有的回调函数都用匿名函数
1.无法控制代码在哪里使用【匿名函数只在被使用的地方才存活,而不是在绑定事件回调时存活】
2.会影响调试【异常发生时,很难分辨出是那个回调函数导致了问题】
2.模式
I/O模式有三种:无序的并行I/O、顺序串行I/O、有序的并行I/O
【无序的并行I/O】操作I/O时,只要扔出请求然后等待结果就好了-->无序不是乱序,而是顺序无法保证
例子:
fs.readFile('foo.txt','utf8',function(err,data){ console.log(data); }); fs.readFile('foo.txt','utf8',function(err,data){ console.log(data); });
【顺序串行I/O】每个任务必须在上一个任务完成后才能开始-->嵌套回调方法
例子:
server.on('request',function(req,res){ //从memcached里获取session信息 memcached.getSession(req,function(session){ //从db获取信息 db.get(session.user,function(userData){ //其他Web服务调用 ws.get(req,function(wsData){ //渲染页面 page=pageRender(req,session,userData,wsData); //输出响应内容 res.write(page); }); }); }); });
**以上例子可读性较差,可以用以下几种方法修改
【回调函数中的命名函数】
server.on('request',getMemCached(req,res){ memcached.getSession(req,getDbInfo(session){ db.get(session.user,getWsInfo(userData){ ws.get(req,render(wsData){ //渲染页面 page=pageRender(req,session,userData,wsData); //输出响应内容 res.write(page); }); }); }); });
【用声明函数把代码分离】**当状态不需要在3个以上回调函数中共享时比较合适
var render=function(wsData){ page=pageRender(req,session,userData,wsData); }; var getWsInfo=function(userData){ ws.get(req,render); }; var getDbInfo=function(session){ db.get(session.user,getWsInfo); }; var getMemCached=function(req,res){ memcached.getSession(req,getDbInfo); };
**让数据再函数间传递有许多方法-->一般使用javascript语言本身的特性
1.javascript有函数作用域,函数内定义的变量仅函数内可见
2.内嵌的回调函数中可以访问外部回调函数内定义的变量,即使外部函数已经返回并关闭了也依旧可以访问
3.当嵌套回调函数时,我们隐式地把所有之前回调函数中的变量都绑定到最新定义的回调函数内
--->仍然用展开的重构方法,再创建一个原始请求都包含的共享作用域,用一个闭包把所有回调函数都包含进去
--->这样,所有的初始请求相关的回调函数都被封装起来,并通过闭包内的变量共享状态
【在回调函数中封装】
server.on('request',function(req,res){ var render=function(wsData){ page=pageRender(req,session,userData,wsData); }; var getWsInfo=function(userData){ ws.get(req,render); }; var getDbInfo=function(session){ db.get(session.user,getWsInfo); }; var getMemCached=function(req,res){ memcached.getSession(req,getDbInfo); }; });
【中间件方式】如:Connect/Express框架中的模块
**由于javascript中对象是以引用的方式传递的,修改对象的值会修改所有该对象的引用,会有很大的风险
-->方法:所有需要依赖某状态的函数通过统一的接口来互相传递
-->故中间件的形式:function(req,res,next)
3.差错处理
**不能用javascript的try/catch来处理
-->因为当回调函数调用时,程序早已经不在原来那个栈了,而try/catch只有当错误发生在内联位置才有用
**用error时间来捕捉错误并处理
例子:
var http=require('http'); var opts={ host:'dskjakjglaljlelg.net', port:80, path:'/' }; var req=http.get(opts,function(res){ console.log('This will never get called'); }); req.on('error',function(e){ console.log('got that pesky error trapped'); });
4.使用多处理器
**cluster模块可以将任务分配给子线程
-->共享程序段给子线程后,主线程不会参与到具体事务中,请求直接连接到子线程
例子:【使用集群来分发任务】
var cluster=require('cluster'); var http=require('http'); var numCPUs=require('os').cpus().length; if(cluster.isMaster){ //创建工作进程 for(var i=0;i<numCPUs;i++){ cluster.fork(); } cluster.on('death',function(worker){ console.log('worker '+worker.pid+' died'); }); }else{ //工作进程创建http服务器 http.Server(function(req,res){ res.writeHead(200); res.end("hello world!\n"); }).listen(8000); }
**cluster.isMaster 和 cluster.isWorker
1.主进程中,cluster.isMaster为true;cluster.isWorker为false;
2.子进程中,cluster.isMaster为false;cluster.isWorker为true;
**cluster提供了跨平台让多个进程共享socket的方法,即多个子进程共享一个端口上的连接
-->上例中为工作进程创建http服务器并设置监听
**cluster还可以判断子进程的健康状态
-->上例中 cluster.on('death',function(worker){...};
**cluster还可以监控工作进程状态,包括内存使用量(用memory属性)
**cluster还可以通过监控子进程多长时间没有变化,找出僵尸进程,并将其杀死