nodejs Stream使用中的陷阱

      最近公司有個專供下載文件的http服務器出現了內存泄露的問題,該服務器是用node寫的,後來測試發現只有在下載很大文件的時候纔會出現內存泄露的狀況。最後乾脆抓了一個profile看看,發現有不少等待發送的buff佔用着內存,個人profile以下(怎麼抓取profile,你們能夠google一下):   node

因而查看了一下發送數據的代碼,以下:     緩存

    var fReadStream = fs.createReadStream(filename);
    fReadStream.on('data', function (chunk) {
        res.write(chunk);
    });
    fReadStream.on('end', function () {
        res.end();
    });

開始以爲沒有什麼問題,因而在google上查了一下node http處理大文件的方法,結果發現有人使用pipe方法,因而將代碼修改以下:  服務器

    var fReadStream = fs.createReadStream(filename);
    fReadStream.pipe(res)

測試了一下,發現OK,可是仍是不明白爲何會這樣,因而研究一個一下pipe方法的代碼,發現pipe有以下代碼:函數

function pipeOnDrain(src) {//可寫流能夠執行寫操做
  return function() {
    var dest = this;
    var state = src._readableState;
    state.awaitDrain--;
    if (state.awaitDrain === 0)
      flow(src);//寫數據
  };
}

function flow(src) {//寫操做函數
  var state = src._readableState;
  var chunk;
  state.awaitDrain = 0;

  function write(dest, i, list) {
    var written = dest.write(chunk);
    if (false === written) {//判斷寫數據是否成功
      state.awaitDrain++;//計數器
    }
  }

  while (state.pipesCount && null !== (chunk = src.read())) {

    if (state.pipesCount === 1)
      write(state.pipes, 0, null);
    else
      state.pipes.forEach(write);

    src.emit('data', chunk);

    // if anyone needs a drain, then we have to wait for that.
    if (state.awaitDrain > 0)
      return;
  }

  // if every destination was unpiped, either before entering this
  // function, or in the while loop, then stop flowing.
  //
  // NB: This is a pretty rare edge case.
  if (state.pipesCount === 0) {
    state.flowing = false;

    // if there were data event listeners added, then switch to old mode.
    if (EE.listenerCount(src, 'data') > 0)
      emitDataEvents(src);
    return;
  }

  // at this point, no one needed a drain, so we just ran out of data
  // on the next readable event, start it over again.
  state.ranOut = true;
}

原來pipe方法每次寫數據的時候,都會判斷是否寫成功,若是寫失敗,會等待可寫流觸發"drain"事件,表示可寫流能夠繼續寫數據了,而後pipe纔會繼續寫數據。oop

     這下明白了,咱們第一次使用的代碼沒有判斷res.write(chunk)是否執行成功,就繼續寫,這樣若是文件比較大,而可寫流的寫速度比較慢的話,會致使大量的buff緩存在內存中,就會致使內存撐爆的狀況。測試

總結:ui

      在使用流的過程當中,必定要注意可讀流和可寫流讀和寫之間的平衡,負責會致使內存泄露,而pipe就實現了這樣的功能。稍微研究了一下文檔,發現stream類有pause()和resume()兩個方法,這樣的話咱們也能夠本身控制讀寫的平衡。代碼以下:this

var http = require("http");
var fs = require("fs");
var filename = "file.iso";

var serv = http.createServer(function (req, res) {
    var stat = fs.statSync(filename);
    res.writeHeader(200, {"Content-Length": stat.size});
    var fReadStream = fs.createReadStream(filename);
    fReadStream.on('data', function (chunk) {
        if(!res.write(chunk)){//判斷寫緩衝區是否寫滿(node的官方文檔有對write方法返回值的說明)
            fReadStream.pause();//若是寫緩衝區不可用,暫停讀取數據
        }
    });
    fReadStream.on('end', function () {
        res.end();
    });
    res.on("drain", function () {//寫緩衝區可用,會觸發"drain"事件
        fReadStream.resume();//從新啓動讀取數據
    });
});

serv.listen(8888);
相關文章
相關標籤/搜索