【问题标题】:Node.js Web Server fs.createReadStream vs fs.readFile?Node.js Web 服务器 fs.createReadStream 与 fs.readFile?
【发布时间】:2016-10-20 14:54:42
【问题描述】:

所以我在纯 node.js 中编写我的 Web 服务器,只使用 bluebird 来代替 promisify。这已经困扰了我一个星期,我无法决定我应该真正使用哪个。我已经阅读了大量关于这两个主题的帖子、博客和文档,请根据您自己的工作经验回答,谢谢。这是详细的总结和相关问题。

这两种方法都已经过测试,它们都非常有效。但是我无法测试性能,我只有自己的基本网站文件(html、css、img、小型数据库等),我从来没有管理过视频文件和大型数据库。

下面是代码部分,给你一些基本的想法(如果你真的知道用哪一个,就不要费心阅读代码,为你节省一些时间),这个问题不是关于逻辑的,所以你可以阅读虚线之间的部分。

关于fs.createReadStream:
优点:适用于大文件,一次读取一个块,节省内存,管道非常智能。
缺点:同步,不能被承诺(流是一个不同的概念来承诺,太难了,不值得)。

//please ignore IP, its just a custom name for prototyping.
IP.read = function (fpath) {
    //----------------------------------------------------
    let file = fs.createReadStream(fpath);
    file.on('error', function () {
        return console.log('error on reading: ' + fpath);
    });
    return file;
    //----------------------------------------------------
};
//to set the response of onRequest(request, response) in http.createServer(onRequest).
IP.setResponse = function (fpath) {
    let ext = path.extname(fpath),
        data = IP.read(fpath);
    return function (resp) {
        //----------------------------------------------------
            //please ignore IP.setHeaders.
        resp.writeHead(200, IP.setHeaders(ext));
        data.pipe(resp).on('error', function (e) {
            cosnole.log('error on piping ' + fpath);
        });
        //----------------------------------------------------
    }
};

关于fs.readFile:
优点:异步,可以很容易地被承诺,这使得代码非常容易编写(开发)和阅读(维护)。还有其他一些我还没接触过的好处,比如数据验证、安全性等。
缺点:不适合大文件。

IP.read = function (fpath) {
    //----------------------------------------------------
    let file = fs.readFileAsync(fpath);
    return file;
    //----------------------------------------------------
};
//to set the response of onRequest(request, response) in http.createServer(onRequest).
IP.setResponse = function (fpath) {
    const ext = path.extname(fpath);
    return function (resp) {
        //----------------------------------------------------
        IP.read(fpath).then((data) => {
            resp.writeHead(200, IP.setHeaders(ext));
            resp.end(data);
        }).catch((e) => {
            console.log('Problem when reading: ' + fpath);
            console.log(e);
        });
        //----------------------------------------------------
    }
};

以下是我的选择:
• 简单的方法:使用fs.createReadStream 处理一切。
• 正确方法:仅对大文件使用fs.createReadStream
• 实用的方法:使用fs.readFile 处理所有问题,直到出现相关问题,然后使用fs.createReadStream 处理这些问题。

我的最终决定是仅将 fs.createReadStream 用于大文件(我将为大文件创建一个函数),并将 fs.readFile 用于其他所有内容。这是一个好的/正确的决定吗?有更好的建议吗?

P.S.(不重要):
我真的很喜欢自己构建基础设施,给你一个想法,当我实例化一个服务器时,我可以像这样设置路由,并自定义我想要的任何东西。请不要建议我使用框架:

let routes = [
    {
        method: ['GET', 'POST'],
        uri: ['/', '/home', '/index.html'],
        handleReq: function () {return app.setResp(homeP);}
    },
    {
        method: 'GET',
        uri: '/main.css',
        handleReq: function () {return app.setResp(maincssP);}
    },
    {
        method: 'GET',
        uri: '/test-icon.svg',
        handleReq: function () {return app.setResp(svgP);}
    },
    {
        method: 'GET',
        uri: '/favicon.ico',
        handleReq: function () {return app.setResp(iconP);}
    }
];

或者我可以自定义它并将其放入config.json 文件中,如下所示:

{
"routes":[
    {
        "method": ["GET", "POST"],
        "uri": ["/", "/home"],
        //I will create a function(handleReq) in my application to handle fpath
        "fpath": "./views/index.html"
    },
    {
        "method": "GET",
        "uri": "/main.css",
        "fpath": "./views/main.css"
    },
    {
        "method": "GET",
        "uri": "/test-icon.svg",
        "fpath": "./views/test-icon.svg"
    }
]
}

【问题讨论】:

  • 总是使用fs.createReadStream() 会出现什么问题?它有效,易于使用,内存消耗要好得多,而且流本身不是同步的,因此不会阻塞您的应用。
  • 相关:github.com/petkaantonov/bluebird/issues/225 - rl;dr:您无需承诺createReadStream(),因为它会立即返回。不要“仅仅因为”过度使用承诺。
  • 致 robertklep:正如我所说,两种实现都没有问题。感谢您的引用,“流本身不是同步的,因此它不会阻止您的应用程序”,确实已经解决了问题。是的,我会选择简单的方法。
  • 致 Tomalak:是的,我几天前读过那篇文章。而且我知道承诺只返回一个结果,与流的行为不同,随着时间的推移会产生结果。感谢您提醒我不要仅仅因为我觉得它很酷而过度使用承诺。

标签: node.js asynchronous stream promise readfile


【解决方案1】:

我们来讨论一下实际可行的方法。

您不应该在生产环境中从 Node.js 提供静态文件

createReadStreamreadFile 都非常有用 - createReadStream 在大多数情况下效率更高,如果您正在处理大量文件(而不是提供文件),请考虑使用它。

无论如何,您都应该从静态文件服务器提供静态文件 - 大多数 PaaS Web 主机会自动为您执行此操作,如果您 set up an environment yourself 您会发现自己的反向代理节点无论如何都位于应该提供静态文件的 IIS 之类的东西后面.

这仅适用于静态文件,同样,如果您多次阅读它们并对其进行转换,您的问题就会变得非常相关。

其他用途,可以放心使用fs.readFileAsync

我经常使用 readFile 将文件读取到缓冲区并使用它们,而 createReadStream 可以改善延迟 - 总体而言,您应该获得相似的吞吐量,并且 API 更易于使用且级别更高。

总之

  • 如果您提供静态文件并关心性能 - 无论如何都不要在生产环境中使用 Node.js。
  • 如果您将文件转换为流并且延迟很重要,请使用createReadStream
  • 否则更喜欢readFile

【讨论】:

  • 感谢有关静态文件的提示。不过我对静态文件不感兴趣,它们一点也不好玩。我的意思是您可以使用 github、dropbox(所有这些云存储服务)等轻松构建静态网站。我从未构建过任何应用程序。在 node.js 中,我发现自己在不知不觉中构建应用程序,一开始,我认为它只是一个更酷的 PHP。就像这个 Web 服务器应用程序一样,我实际上正在构建类似 Apache/Nginx 的东西。我先尝试静态文件,接下来我将做模板引擎和数据库交互,乐趣不断。
  • 对于这篇文章,我决定采用简单的方法,使用 createReadStream。毕竟,保持一致也很重要(来自难以保持一致的人说)。
  • 模板应该缓存在内存中,所以fs.readFile 完全没问题 - 考虑使用模板引擎,无论如何它都会为你做这件事 - 其他动态服务器上的其他静态内容仍应由 iis/nginx 提供.
  • 是的,我正在考虑使用车把,但首先我只想学习如何构建一个。
    **你能告诉我为什么使用 node.js 来处理静态内容如此糟糕吗?** 如果这太难解释了,几个提示/单词会很好。这样我就可以开始在我的 Web 服务器应用程序中实现相关的解决方案了。
  • 我发现了一些东西,比如“在性能方面,像 Apache/Nginx 这样的传统 Web 服务器使用称为 sendfile(2) 的系统调用,它将静态资源从磁盘直接复制到网卡绕过进程内存(RAM)...”,我会继续阅读和搜索。感谢您的提醒,因为没有人告诉我要注意这一点(在现实生活中,我一个人)。
猜你喜欢
  • 2017-09-01
  • 2011-06-03
  • 1970-01-01
  • 2014-09-23
  • 1970-01-01
  • 1970-01-01
  • 2011-07-25
  • 2013-05-23
  • 1970-01-01
相关资源
最近更新 更多