【问题标题】:Node.js network performance suffers drastically under load testing on AWS在 AWS 上进行负载测试时,Node.js 网络性能大幅下降
【发布时间】:2014-11-03 21:41:10
【问题描述】:

我将以下 Node.js 代码编写为非常基本的 HTTP 服务器。它的目的是摄取大量包含 base64 数据的请求,并将该数据作为图像写入 S3。 S3 编写方面运行良好,没有任何问题。但是,初始请求在负载下似乎花费了异常长的时间。

server.js

http.createServer(function(req, res){
    if (url.parse(req.url).pathname == '/processimage' && req.method.toLowerCase() == 'post') {
        var startTime = new Date();
        var rawBody = '';
        req.on('data', function(chunk) { 
            rawBody += chunk;
        });

        req.on('end', function() {
            console.log('REQUEST FINISHED: ' + (new Date() - startTime) + ' ms');
            // Process image, upload to S3
            res.writeHead(200);
            res.end('data');
        }
        return;
    } else {
        // Other requests
    }
}).listen(1347);

我也在计时图像处理部分,但它的表现很好,与这个问题无关。

为了对此进行测试,我编写了一个 POST 测试数据的脚本,其中包含大约 500k 个字符的 base64(2-3mb 原始图像)。在本地测试时,一切正常。我的输出是:

REQUEST FINISHED: 9ms
REQUEST FINISHED: 23ms
REQUEST FINISHED: 18ms

等等

但是,在 x-large 实例上将代码部署到 AWS 后,我看到以下内容:

REQUEST FINISHED: 499ms
REQUEST FINISHED: 2493ms
REQUEST FINISHED: 1784ms
REQUEST FINISHED: 3440ms
REQUEST FINISHED: 994ms
REQUEST FINISHED: 36043ms

基本上,在对此进行压力测试时,每 30 个请求中大约有 1 个请求似乎需要 10 多秒(在某些情况下甚至 30 多秒)才能通过请求管道。正如您在我的代码中看到的那样,在计算时间之前对数据进行了零处理,因此这意味着介于“req.on('data')”和“req.on('end')”之间,有很大的延迟。

我的问题是:在 req.on('data') 和 req.on('end') 之间是否发生某种处理会导致此 POST 花费这么长时间?主机是否可能由于某种原因(Ubuntu 12.04、x-large 实例、14GB 内存、4x CPU)阻塞了这些请求?

【问题讨论】:

  • 如果您同时运行大量请求,那么您可能只是看到单个 node.js 线程同时处理多个请求,因此任何单个请求的总吞吐量时间都会延长只要有异步操作,它就会在每个请求之间切换。您没有显示 node.js 服务器实际上如何处理它接收到的数据,这可能与问题非常相关,因为这可能是一堆 CPU 的去向。
  • 还请记住,数据不会一次全部到达。它以块的形式到达,每个块触发一个req.on('data') 回调。可能需要许多回调来提供 500k 块数据。如果您一次向服务器抛出大量请求,您的代码将同时“进行中”多个请求(每个请求都有自己的请求对象),您将从请求 A 中获得一个块,然后从请求 A 中获得一个块请求 B 等等。它不会先执行所有请求 A,然后执行请求 B。
  • @jfriend00 谢谢 - 我实际上已经删除了所有处理代码以缩小问题范围。完整的代码如下所示: req.on('end', function() { // console.log... res.writeHead(200); res.end('success'); } 你是正确的,块占用一段时间后,预计会有一些网络延迟。然而,当使用相同的 base64 代码重复请求时,时间范围从 300ms(可接受)到 36000(根本不)似乎很奇怪。
  • 可能存在连接 base64 块的内存瓶颈。尝试push将块放入req.on('data',....join() 中的数组中req.on('end',...。通过简单地连接字符串,您要求垃圾收集器收集前一个字符串。在高负载下,这要么缩短了处理请求的时间,要么节点达到了 1.5gb 的内存限制

标签: node.js networking amazon-web-services


【解决方案1】:

这可能需要大量的日志记录才能看到发生了什么。您有多个请求,每个请求都包含大量数据,这些数据将在多个块中进行处理。我想说的第一件事是准确记录每个请求的每个块的处理时间,然后您可以了解事情发生的顺序并查看其结果。这是记录的第一个想法:

// helper function
logDelta(id, start, msg) {
    var delta = new Date() - start;
    console.log(id + ": (" + delta + ") - " + msg);
}

var reqCntr = 0;
http.createServer(function(req, res){
    if (url.parse(req.url).pathname == '/processimage' && req.method.toLowerCase() == 'post') {
        // place an id on the request for logging purposes
        req.trackerID = reqCntr++;
        console.log(req.trackerID + ": Begin Request");

        var startTime = new Date();
        var rawBody = '';
        var chunkCntr = 0;
        req.on('data', function(chunk) { 
            logDelta(req.trackerID, startTime, "chunk(" + chunkCntr + "), length = " + chunk.length);
            ++chunkCntr;
            rawBody += chunk;
        });

        req.on('end', function() {
            logDelta(req.trackerID, startTime, "Request Finished");
            // Process image, upload to S3
            res.writeHead(200);
            res.end('data');
        }
        return;
    } else {
        // Other requests
    }
}).listen(1347);

然后,您可能需要对日志数据进行一些处理,以便能够跟踪每个请求中每个事件的时间,尤其是长时间运行的请求。这很可能会为您提供下一步寻找方向的线索。


仅供参考,堆栈中有很多不同的地方可能会遇到瓶颈。例如,如果您在服务器上触发数据的速度超过了数据的处理速度(无论是在操作系统级别还是在节点级别),那么 TCP 缓冲区将在某个时候填满,并且传入的数据包将被丢弃,或者套接字将被放入某种流控制中。

如果您在共享服务器上运行,您也可能无法访问整个 TCP 缓冲区。


这是一个方案,它会收集每个连接的所有历史记录,然后立即将其输出到日志中(使单个连接的剖析更容易,但会掩盖不同连接之间的事件顺序)。

var reqCntr = 0;
http.createServer(function(req, res){
    var log = [];
    var id;
    var startTime = new Date();

    // helper function
    logDelta(msg) {
        var delta = new Date() - startTime;
        log.push(id + ": (" + delta + ") - " + msg);
    }

    if (url.parse(req.url).pathname == '/processimage' && req.method.toLowerCase() == 'post') {
        // place an id on the request for logging purposes
        id = reqCntr++;
        log.push(id + ": Begin Request");

        var rawBody = '';
        var chunkCntr = 0;
        req.on('data', function(chunk) { 
            logDelta("chunk(" + chunkCntr + "), length = " + chunk.length);
            ++chunkCntr;
            rawBody += chunk;
        });

        req.on('end', function() {
            // dump connection history to console.log()
            logDelta("Request Finished");
            console.log(log.join("/n"));
            // Process image, upload to S3
            res.writeHead(200);
            res.end('data');
        }
        return;
    } else {
        // Other requests
    }
}).listen(1347);

【讨论】:

  • 这非常有用 - 我将它添加到我的代码中并且能够查看响应时间。在负载测试之前它仍然可以正常工作,在此期间某些请求将花费 20 多秒,但现在它实际上显示了单个块:块 1:3 毫秒,块 2:1000 毫秒等。所以在接收第二块的某个地方,很多时间通过了。
  • @MattFuller - 您可以运行许多 Unix 实用程序来检查 TCP 级别发生的情况,您可以在其中查看有关流量控制、丢弃数据包、重新传输等的统计信息……我不要立即记住它们,但您可能可以通过 Google 找到它们。您可能想查看下一个 TCP 级别是否存在问题。如果数据包的到达速度快于处理速度,您可能需要更大的 TCP 缓冲或其他一些 TCP 调整。
猜你喜欢
  • 2018-11-16
  • 1970-01-01
  • 2019-11-21
  • 1970-01-01
  • 2015-06-30
  • 2012-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多