node处理高并发的几种方案

9,651 阅读3分钟

粘在开头的废话

  • 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可以拿到各个时间点的监听事件(可自行百度查看