粘在开头的废话
- nodejs高并发大流量的设计实现,控制并发的三种方法 eventproxy、async.mapLimit、async.queue控制并发 Node.js是建立在Google V8 JavaScript引擎之上的网络服务器框架,允许开发者能够用客户端使用的语言JavaScript在服务器端编码。
-
node.js优缺点: 优点: 高并发,io密集型处理, 可以作为单页面应用,便于爬虫抓取。 缺点:不适合cpu计算密集型, 对关系数据库支持不好
-
nodejs高并发大流量的设计实现
-
原理:非阻塞事件驱动实现异步开发,通过事件驱动的I/O来操作完成跨平台数据密集型实时应用 传统的server 每个请求生成一个线程, nodejs是一个单线程的,使用libuv保持数万并发
直接开始第一种
-
eventproxy
eventproxy是使用类似递归的方式,让每次最大同时请求数量控制在设定的值
import useSuperagent from '../tools/superagent';
import cheerio from 'cheerio'
import eventproxy from 'eventproxy'
import url from 'url'
/*
eventProxy 是发起异步请求 可以用递归控制请求数量
*/
const baseUrl = 'https://cnodejs.org/';
const ep = new eventproxy();
let start = 0;
const maxProxy = 10;
function NodeProxy (req, res) {
start = 0;
useSuperagent(baseUrl, function (html) {
// cheerio 就是node jquery
const $ = cheerio.load(html);
const items = [];
$('#topic_list .topic_title').each(function (idx, element) {
const $element = $(element);
items.push(url.resolve(baseUrl, $element.attr('href')));
});
console.log(items.length, 'items');
takeComment(items, res, start);
// res.send(items);
})
}
function takeComment (urls, res, _start = 0) {
ep.after('output', maxProxy, function (html) {
console.log('start:', start, 'urls:', urls.length);
if (start < urls.length) {
takeComment(urls, res, start);
saveHtml(html, false)
} else {
saveHtml(null, true, res)
}
});
for (let i = _start; i < urls.length; i++) {
if (i > _start + maxProxy) break;
// console.log('插入队列下表', i);
useSuperagent(urls[i], function (html) {
ep.emit('output', [urls[i], html]);
});
start++;
}
}
let htmls = [];
function saveHtml (html, done, res) {
if (done) return res.send(htmls);
htmls = htmls.concat(html.map(topicPair => {
var topicUrl = topicPair[0];
var topicHtml = topicPair[1];
var $ = cheerio.load(topicHtml);
return ({
title: $('.topic_full_title').text().trim(),
href: topicUrl,
comment1: $('.reply_content').eq(0).text().trim(),
});
}));
}
export default NodeProxy
第二种
- async.queue
是使用async库 一个流程控制器 详情直接看代码 相信你肯定能看懂
'use strict';
import async from 'async'
/*
async.queue 流程控制器
*/
const maxCount = 10;
let urls = []
for (let i = 0; i < 50; i++) {
urls.push(i)
}
let count = 0;
function axios (url, callback) {
const delay = parseInt((Math.random() * 10000000) % 2000, 10);
count++;
console.log('现在的并发数是', count, ',正在抓取的是', url, ',耗时' + delay + '毫秒');
setTimeout(function () {
// 调用成功之后 并发数量 -1
count--;
//抓取成功,调用回调函数
callback(null, url + ' html content');
}, delay);
};
let q = async.queue(function (url, callBack) {
axios(url, function () {
callBack(url);
});
}, maxCount);
function Queue (req, res) {
const htmls = [];
/* internet上用的 赋值的方法 一直报错。。后来发现是用回调的方式监听... */
// q.drain = function () {
// console.log(123);
// }
q.drain(function () {
console.log(htmls.length, '个请求完成');
res.send(htmls);
})
urls.forEach(url => {
q.push(url, function (url) {
htmls.push(url);
});
})
}
export default Queue
第三种
-
async.mapLimit
原理与async.queue相类似 都自带控制最大任务执行数量
import async from 'async'
const maxCount = 10; // 最大并发数量
let count = 0;
function axios (url, callback) {
const delay = parseInt((Math.random() * 10000000) % 2000, 10);
count++;
console.log('现在的并发数是', count, ',正在抓取的是', url, ',耗时' + delay + '毫秒');
setTimeout(function () {
// 调用成功之后 并发数量 -1
count--;
//抓取成功,调用回调函数
callback(null, url + ' html content');
}, delay);
};
const AsyncStart = function (req,res) {
let urls = []
for (let i = 0; i < 50; i++) {
urls.push(i)
}
async.mapLimit(urls, maxCount, function (url, callBack) {
axios(url, callBack)
}, function (err, result) {
console.log('final', err);
console.log(result);
res.send(result)
})
}
export default AsyncStart
区别
- eventproxy
通过ep.after('output', 监听任务执行
任务执行结束之后调用 ep.emit 通知eventproxy 等全部任务都emit之后 调用after回调函数
- async.queue
let q = async.queue(function (url, callBack) {
axios(url, function () {
callBack(url);
});
}, maxCount);
maxCount自带的控制上限效果
通过callback通知queue任务执行完成,可以执行下一个了
- async.mapLimit
原理基本与queue相类似 有点不同的是queue可以拿到各个时间点的监听事件(可自行百度查看