Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node终结process #2

Open
yangchongduo opened this issue Jul 16, 2017 · 1 comment
Open

node终结process #2

yangchongduo opened this issue Jul 16, 2017 · 1 comment

Comments

@yangchongduo
Copy link
Owner

玩转node process

事件驱动

为了处理高并发问题,事件驱动的服务器模式出现,node与nginx都是基于事件驱动实现的。
采用单线程避免了不必要的内存开销和上下文切换开销

  • 内存开销: 开启一个线程或者进程都会占用一定的内存。
  • 上下文切换:一个线程处理完,就会销毁,切换到下一个线程。node所有请求的上下文都是统一的,无需关心

如何充分利用CPU服务器?(child_process)

启动多进程即可。每个进程利用一个cpu,node提供了child_process模块,并且也提供了child_process.fork()函数供我们实现进程的复制
主进程不负责具体的业务处理,而是负责调度工作进程,工作进程负责具体的业务处理
worker.js

var http = require('http');
http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('Hello World\n');
}).listen(Math.round((1 + Math.random()) * 1000), '127.0.0.1');  

master.js

var fork = require('child_process').fork;
var cpus = require('os').cpus();
for (var i = 0; i < cpus.length; i++) {
	fork('./worker.js');
}

以上是通过fork多个进程,(port都是不一样的,后续通过进程之间句柄传递)只是为了充分将CPU资源利用起来,而不是解决并发问题,并发问题是node通过事件驱动的方式在进程上解决的

进程间的通信

子进程对象则由send()方法实现主进程向子进程发送数据,message事件实现收听子进程发来的数据。
parent.js

var cp = require('child_process');
var n = cp.fork(__dirname + '/sub.js');
n.on('message', function (m) {
	console.log('PARENT got message:', m);
});
n.send({hello: 'world'});

sub.js

process.on('message', function (m) {
	console.log('CHILD got message:', m);
});
process.send({foo: 'bar'});

父进程与子进程之间会出创建IPC通道,通过IPC通道,父子进程之间才能通过message和send传递消息;

进程间通信的原理

父进程在创建子进程之前,会创建IPC通道并监听它,然后才真正创建子进程,并通过环境变量(NODE_CHANNEL_FD)告诉子进程这个IPC通道的文件描述符(FD)。子进程在启动的过程中,根据文件描述符趣连接这个已经存在的IPC通道,从而完成父子进程之的连接

句柄传递(实现多个进程监听相同的port)

send方法除了能够通过IPC发送数据外,还能发送句柄

child.send(message,[sendHandle])
  • 句柄 :句柄是一种可以用来标识资源的引用,他的内部包好了针对对象的文件描述符(FD)。比如句柄可以用来标识一个服务器端socket对象,一个客户端socket对象等。
  • 发送句柄可以使主进程接收到socket请求后,将这个socket直接发送给工作进程,而不是重新与工作进程建立新的socket连接来转发数据。减少文件描述符的使用量。
var child = require('child_process').fork('child.js');
var server = require('net').createServer();
server.on('connection', function (socket) {
	socket.end('handled by parent\n');
});
server.listen(1337, function () {
	child.send('server', server);
});

child.js

process.on('message', function (m, server) {
	if (m === 'server') {
		server.on('connection', function (socket) {
			socket.end('handled by child\n');
		});
	}
})

备注:直接将一个TCP服务发送给子进程。

如何保证进程的健壮性和稳定性?

信号 备注
SIGINT ctrl + c
SIGTERM 软件终止信号
error 当子进程无法被复制创建,无法被杀死,无法发送信息是灰触发改事件
exit 子进程退出时触发改事件,子进程如果正常退出,这个事件的第一个参数为退出码,否则为null。如果进程是通过kill()方法杀死的,会得到第二个参数,它表示杀死进程时的信号。
close 在子进程的标准输入输出流终止时触发该事件,参数与exit相 同。
disconnect 在父子进程调用disconnect()方法时触发该事件,在调用该方法时将关闭监听IPC通道。
方法 作用
kill 不是真正的将IPC相连的子进程杀死,它只是给子进程发送一个系统信号(SIGTERM)
close 不再接受新的请求,处理完此次请求,进程退出
master.js
var fork = require('child_process').fork;
var cpus = require('os').cpus();
var server = require('net').createServer();// tcp服务
server.listen(1337);
var workers = {};
var createWorker = function () {
    var worker = fork(__dirname + '/worker.js');
  // 接受子进程的信号,在错误子进程退出之前就启动新子进程,
    worker.on('message',function (message) {
      if(message.act == 'suicide'){
				createWorker();
      }
		});
    // 子进程exit退出
    worker.on('exit', function () {
        console.log('Worker ' + worker.pid + ' exited.'); 
        delete workers[worker.pid];
    });
    // 发送句柄
    worker.send('server', server);
    workers[worker.pid] = worker;
    console.log('Create worker. pid: ' + worker.pid);
};
// 启动对应的进程数
for (var i = 0; i < cpus.length; i++) {
    createWorker();
}
process.on('exit', function () {
    for (var pid in workers) {
        workers[pid].kill();
    }
});

worker.js

var http = require('http');
var server = http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/plain'});
	res.end('handled by child, pid is ' + process.pid + '\n');
	lll
});
var worker;
process.on('message', function (m, tcp) {
	if (m === 'server') {
		worker = tcp;
		worker.on('connection', function (socket) {
			server.emit('connection', socket);
		});
	}
});
// 捕获异常错误
process.on('uncaughtException', function (err) {
	//  录日志
	logger.error(err);
	// 发送自杀信号
	process.send({act: 'suicide'});
	// close保持当前请求清理完之后退出。不在接受新的请求。
	worker.close(function () {
		process.exit(1);
	});
});

cluster(node原生模块)

cluster用以解决多多核CPU的利用率的问题。

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers
	for (var i = 0; i < numCPUs; i++) {
		cluster.fork();
	}
	cluster.on('exit', function (worker, code, signal) {
		console.log('worker ' + worker.process.pid + ' died');
	});
} else {
	// In this case its a HTTP server
	http.createServer(function (req, res) {
			res.writeHead(200);
			res.end("hello world\n");
		}).listen(8000);
}

事实上cluster模块就是child_process和net(提供tcp服务)模块的组合应用。

pm2

pm2是内建负载均衡,后台运行,0秒的重载。 PM2是完美的。
pm2的两种运行模式:

  • fork :基于child_process的fork模式,用于开发模式,单个进程
  • cluster:node cluster 集群模块,

egg

egg是阿里提供的一种企业级的框架。egg不在依赖于pm2守护node进程,同时提供了各种约定,非常不错
在框架里,我们采用 gracefulegg-cluster 两个模块配合实现上面的逻辑。这套方案已在阿里巴巴和蚂蚁金服的生产环境广泛部署,且经受过『双11』大促的考验,所以是相对稳定和靠谱的。

备注
cluster原理

简述token

token是现在防止csrf的攻击比较流程的方案:

  • 方案1: RD放在session中,FE不需要做任何事,RD获取之后从redis根据key,校验,检验沟通过之后,更新redis的expire时间。
app.use(session({
    key: "oo",
    store: new RedisStore({
      host:config.redis.host,
      port:config.redis.port,
      auth_pass:config.redis.password || ''
    }),
    cookie: config.session.cookie
  }));
  • 方案2: 不放在session里面,通过接口的参数传递到FE,由FE决定种植在cookie || localstorage

备注: 两者的方式之后token的种植的方式不一样。

redis一些使用场景

redis队列缓存是高并发必备法器,Redis运行在内存中但是可以持久化到磁盘,比起每次查询数据库要快很多

  • 首页的数据不会发生大的变动,这个可以通过redis缓存一到两天左右。
  • 产品详情页根据sku_id缓存数据
  • token存放

句柄详解

send()方法在将消息发送带IPC管道前,将消息组装成两个对象,一个参数是handle,一个是message

{
	cmd: 'NODE_HANDLE', 
	type: 'net.Server', 
	msg: message
}
@wanglele16
Copy link

好厉害啊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants