multi-node和generic-pool两大利器
1、multi-node
node只能单进程,单cpu工作,而multi-node则可以让node在多进程下共享内存的工作,实现机制是依靠child_process的sendmsg做到的。想要了解具体实现原理请查阅multi-node的源代码。
我们可以从github上下载到最新版本的multi-node,地址是:https://github.com/kriszyp/multi-node
他的具体用法也非常的简单,我们看示例代码:
var server = require("http").createServer(function(request, response){
//这里是处理请求的程序
});
var nodes = require("multi-node").listen({
port: 80, //监听的端口
nodes: 4 //打开node进程数,一般最多为服务器CPU个数
}, server);
这样使用 $ node server.js就会启动4个server.js来监听80端口,具体请求被分配到哪个进程,由multi-node控制。在进程之间通信可以利用他提供的Framing,也可以利用redis等内存db来做。
nodes.addListener("node", function(stream){
stream = require("multi-node").frameStream(stream);
stream.addListener("message", function(data){
//在这里接受从其他进程发送的数据,可以是字符串,对象或其他格式
});
stream.send({foo:"bar"}); //类似广播概念,给所有进程广播这个消息
});
单进程"hello world"node服务器,在4CPU 3.4GHZ服务器上压测是4000qps+,开了multi-node以后再测试可以稳定达到7500qps+以上,峰值在8500qps+,性能大幅度提高。
2、generic-pool
generic-pool是一个通用连接池,node和php不同,php每次和db通信,都需要先建立连接然后执行程序,最后释放这个连接。node是可以事先建立一个池子,然后根据需要生成连接放入这个池中,当node要和db通信时则去池子中拿连接,用完以后不释放,而是将这个连接归还到池中,我们也可以设定这个池子的的最大连接数和闲置连接超时释放的时间。
1、可以不必为建立连接和释放连接消耗更多的响应时间,一个连接在高并发下可以被高度重用。
2、有队列机制,当池子中连接都用完了,其他请求在排队时,有一些紧急的任务需要插队,我们可以很方便的做到这点。
我们可以从github上下载到最新版本的generic-pool,地址是:https://github.com/coopernurse/node-pool
他的示例代码是为mysql提供连接池,我就不贴上来了,我们来为redis做一个简单的链接池,并测试下。先简单介绍下他的几个参数:
name : name of pool (string, optional)
create : function that returns a new resource
should call callback() with the created resource
destroy : function that accepts a resource and destroys it
max : maximum number of resources to create at any given time
idleTimeoutMillis : max milliseconds a resource can go unused before it should be destroyed
(default 30000)
reapIntervalMillis : frequency to check for idle resources (default 1000),
priorityRange : int between 1 and x - if set, borrowers can specify their
relative priority in the queue if no resources are available.
see example. (default 1)
log : true/false or function -
If a log is a function, it will be called with log strings
Else if log is true, verbose log info will be sent to console.log()
Else internal log messages be ignored (this is the default)
英文好的朋友很容易就看懂了上面的这些说明,我为英文和我一样比较差的朋友简单翻译一下,省的他们再去google翻译了。
name:连接池的名字,例如“mysql”,主要是用作日志上的区分,一个名字而已
create:一个函数,和db建立连接的函数,必须要把创建好的db连接示例传入到callback函数中
destroy:摧毁和这个db连接的函数
idleTimeoutMillis:为每个连接设置一个超时时间
reapIntervalMillis:检查闲置连接的频率
priorityRange:1-X的整数,当没有连接时,可以为1-X这几个应用开辟绿色通道,让他们插队。
log:日志系统,true的话则是记录详细日志,开发时用,一般为false
下面我就贴一段redis连接池的代码:
var poolModule = require('./generic-pool.js');
var pool = poolModule.Pool({
name : 'redis',
create : function(callback) {
var client = require('redis').createClient(); //这里是创建redis连接实例的代码
callback(null, client); //创建完了以后,要把client传给callback,这里第一个参数看了源码得知是err,我们不必理会
},
destroy : function(client) { client.quit(); }, //当超时则释放连接
max : 10, //最大连接数
idleTimeoutMillis : 30000, //超时时间
log : true,
});
下面我可以利用他提供的几个方法,用连接池来操作redis:
var test_pool =function(){
pool.acquire(function(err, client) {
client.set('test', 'xdy',function(err, reply){
console.log(reply+"");
pool.release(client);
})
});
pool.acquire(function(err, client) {
client.set('test1', 'xdy',function(err, reply){
console.log(reply+"");
pool.release(client);
})
});
pool.acquire(function(err, client) {
client.set('test2', 'xdy',function(err, reply){
console.log(reply+"");
pool.release(client);
})
});
}
test_pool ();
setTimeout(function(){
test_pool ();
},500);
我们定义了一个test_pool的方法,定义了3个redis操作,使用pool的acquire方法来去连接池拿连接,在处理完毕以后,用pool的release方法将连接归还到连接池,我们打开日志,运行程序以后可以看到:
在test_pool()执行后,连接池创建了3个连接给test_pool,test_pool在redis操作完毕以后,归还连接,在500毫秒之后,test_pool再次去连接池拿连接操作redis,这时因为之前的连接没有超时,所以之前的3个连接又被拿出来处理第二次test_pool函数了。这样就做到了重用,不必每次去建立和释放连接,当30秒过后,连接会自动释放掉。
个人觉得我们还可以对这个连接池改写一下,再提高一下连接池在某些情况下的性能,为连接池增加一项配置,就是永不过期的连接数,这样我就可以在程序启动的时候预先放置好一些连接永不过期,如果大并发突然过来可以节约一点创建连接的时间,这种情况比较少,改写以后可能压力测试成绩会好一点。
具体压测数据,请关注我博客,我将会在和PHP对比压测文章内给出。