nodejs高并发大流量的控制并发的三种方法
eventproxy、async.mapLimit、async.queue控制并发。
node.js优缺点:
优点: 高并发,io密集型处理, 可以作为单页面应用,便于爬虫抓取。
缺点:不适合cpu计算密集型, 对关系数据库支持不好
libuv原理:
c语言编写的基础库实现主循环,文件,网络即可
libuv的改进:
回传上下文信息,其它线程不能访问缺省主循环,loop不支持多线程
代码可读性维护改进:
async:
async.waterfall([getcatalog, getaticle,getTigle])
promise 的方法
koa写法
es6写法使用yield
var titles = []
co(function *() {
var catalog = yield getCatalog(gid)
var articles = yield getArticles(catalog)
titles = yield getTitles(articles)
});
0
1
2
3
4
5
vartitles=[]
co(function*(){
varcatalog=yieldgetCatalog(gid)
vararticles=yieldgetArticles(catalog)
titles=yieldgetTitles(articles)
});
用eventproxy、async.mapLimit、async.queue控制并发
1.用eventproxy实现控制并发
var EventProxy = require('eventproxy');
const most = 5;//并发数5
var urllist = [....];//待抓取url列表,100个
function foo(start){
var ep = new EventProxy();
ep.after('ok',most,function(){
foo(start+most);//一个批次任务完成,递归进行下一批任务
});
var q=0;
for(var i=start;i
if(q>=most){
break;//最多添加most个任务
}
http.get(urllist[i],function(res){
//....
res.on('end',function(){
ep.emit('ok');//一个任务完成,触发一次ok事件
});
});
q++;
}
}
foo(0);
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1.用eventproxy实现控制并发
varEventProxy=require('eventproxy');
constmost=5;//并发数5
varurllist=[....];//待抓取url列表,100个
functionfoo(start){
varep=newEventProxy();
ep.after('ok',most,function(){
foo(start+most);//一个批次任务完成,递归进行下一批任务
});
varq=0;
for(vari=start;i
if(q>=most){
break;//最多添加most个任务
}
http.get(urllist[i],function(res){
//....
res.on('end',function(){
ep.emit('ok');//一个任务完成,触发一次ok事件
});
});
q++;
}
}
foo(0);
2.使用 async.mapLimit 控制并发
var async = require('async');
//模拟一组连接地址
var urls = [];
for(var i = 0; i < 30; i++) {
urls.push('http://datasource_' + i);
}
console.log(urls);
// 并发连接数的计数器
var concurrencyCount = 0;
// 并发抓取数据的过程
var fetchUrl = function (url, callback) {
// delay 的值在 2000 以内,是个随机的整数
var delay = parseInt((Math.random() * 10000000) % 2000, 10);
concurrencyCount++;
console.log('现在的并发数是', concurrencyCount, ',正在抓取的是', url, ',耗时' + delay + '毫秒');
setTimeout(function () {
concurrencyCount--;
//抓取成功,调用回调函数
callback(null, url + ' html content');
}, delay);
};
//使用 async.mapLimit 来 5 个并发抓取,并获取结果
async.mapLimit(urls, 5, function (url, callback) {
fetchUrl(url, callback);
}, function (err, result) {
//所有连接抓取成功,返回回调结果列表
console.log('final:');
console.log(result);
});
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
varasync=require('async');
//模拟一组连接地址
varurls=[];
for(vari=0;i<30;i++){
urls.push('http://datasource_'+i);
}
console.log(urls);
// 并发连接数的计数器
varconcurrencyCount=0;
// 并发抓取数据的过程
varfetchUrl=function(url,callback){
// delay 的值在 2000 以内,是个随机的整数
vardelay=parseInt((Math.random()*10000000)%2000,10);
concurrencyCount++;
console.log('现在的并发数是',concurrencyCount,',正在抓取的是',url,',耗时'+delay+'毫秒');
setTimeout(function(){
concurrencyCount--;
//抓取成功,调用回调函数
callback(null,url+' html content');
},delay);
};
//使用 async.mapLimit 来 5 个并发抓取,并获取结果
async.mapLimit(urls,5,function(url,callback){
fetchUrl(url,callback);
},function(err,result){
//所有连接抓取成功,返回回调结果列表
console.log('final:');
console.log(result);
});
3.使用async.queue 控制并发
"use strict"
var http = require('http');
var cheerio = require('cheerio');
var URL = require('url');
var path = require('path');
var fs = require('fs');
var async = require('async');
var baseUrl = "http://cnodejs.org/";
var targetUrl = "http://cnodejs.org/";
var stime = new Date();
function sGet(url,callback){
var chunks = [];
http.get(url,(res)=>{
if (res.statusCode != '200') {
callback({message:"抓取失败,状态码:"+res.statusCode,url:url});
return;
}
res.on('data',(chunk)=>{
chunks.push(chunk);
});
res.on('end',()=>{
callback(null,Buffer.concat(chunks).toString());
});
}).on('error',(e)=>{
callback({message:"抓取失败",url:url,err:e});
});
}
sGet(targetUrl,(err,data)=>{
if (err) {
console.log(err);
return false;
}
var $ = cheerio.load(data);
var anchors = $("#topic_list a.topic_title");
console.log('共'+anchors.length+'个任务');
const most=5;//并发数
//创建队列并指定并发数
var q=async.queue(function(url,callback){
var filename = path.basename(url)+'.txt';
sGet(url, (err, data)=> {
if (err) {
callback(err);
return false;
}
fs.writeFile('./html/' + filename, data, function (err) {
if (err) {
throw err;
}
callback(null,filename);
});
});
},most);
q.drain = function() {
console.log('任务全部完成,共耗时:'+(new Date()-stime)+'ms');
}
anchors.each(function(){
var url = URL.resolve(baseUrl,$(this).attr('href'));
q.push(url,function(err,filename){
if (err) {
console.log(err);
return;
}
console.log("finished:"+filename);
});
});
});
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"use strict"
varhttp=require('http');
varcheerio=require('cheerio');
varURL=require('url');
varpath=require('path');
varfs=require('fs');
varasync=require('async');
varbaseUrl="http://cnodejs.org/";
vartargetUrl="http://cnodejs.org/";
varstime=newDate();
functionsGet(url,callback){
varchunks=[];
http.get(url,(res)=>{
if(res.statusCode!='200'){
callback({message:"抓取失败,状态码:"+res.statusCode,url:url});
return;
}
res.on('data',(chunk)=>{
chunks.push(chunk);
});
res.on('end',()=>{
callback(null,Buffer.concat(chunks).toString());
});
}).on('error',(e)=>{
callback({message:"抓取失败",url:url,err:e});
});
}
sGet(targetUrl,(err,data)=>{
if(err){
console.log(err);
returnfalse;
}
var$=cheerio.load(data);
varanchors=$("#topic_list a.topic_title");
console.log('共'+anchors.length+'个任务');
constmost=5;//并发数
//创建队列并指定并发数
varq=async.queue(function(url,callback){
varfilename=path.basename(url)+'.txt';
sGet(url,(err,data)=>{
if(err){
callback(err);
returnfalse;
}
fs.writeFile('./html/'+filename,data,function(err){
if(err){
throwerr;
}
callback(null,filename);
});
});
},most);
q.drain=function(){
console.log('任务全部完成,共耗时:'+(newDate()-stime)+'ms');
}
anchors.each(function(){
varurl=URL.resolve(baseUrl,$(this).attr('href'));
q.push(url,function(err,filename){
if(err){
console.log(err);
return;
}
console.log("finished:"+filename);
});
});
});