Node.js:同时处理多个非常繁重的请求,对所有请求进行单一响应

问题描述 投票:0回答:2

很抱歉我无法想出更好的标题。

我总是遇到这个问题(当在 node.js 和 python 中编码时),但我认为我的解决方案有点脏。

我来这里是为了寻求更好的解决方案来解决这个问题。

场景如下:

您的服务器正在根据特殊的http请求执行非常非常繁重的任务(例如为URL生成浏览器屏幕截图/生成带有统计信息的游戏服务器横幅)。无论谁向您的服务器发出 HTTP 请求,都会得到相同的响应。响应将被缓存很长时间。

例如,在浏览器屏幕截图生成HTTP请求时,您的服务器应该生成一个phantomjs,捕获屏幕截图,保存并长期缓存,然后用捕获的PNG进行响应。此后的 HTTP 请求应该会命中缓存。

场景伪代码:

server.get(":urlname.png", function(req, res, next) {
    var cached = cache.get(req.params_urlname);
    if (cached) {
        res.send(cached);
        return;
    }
    // This will take very long time
    generateScreenshot(req.params_urlname, function(pngData) {
        cache.set(req.params_urlname, pngData, LONG_TIME);
        res.send(cached);
    });
});

问题是这样的:

假设您有一个屏幕截图生成 URL (http://yourserver.com/generate-screenshot/google.png)。截图 尚未生成或缓存。

您在一个非常受欢迎的论坛中发布了该 URL,并且同时有 1000 个 HTTP 请求对该 URL!这意味着你的服务器必须生成 1000 个 phantomjs,它们一起将同时生成 google.com 的屏幕截图,这太疯狂了!

换句话说,重函数应该只执行一次来生成缓存。

我当前问题的代码解决方案:

var pendingResponse = {};
server.get(":urlname.png", function(req, res, next) {
    var cached = cache.get(req.params_urlname);
    if (cached) {
        res.send(cached);
        return;
    }
    // The screenshot is currently generating for other request. Let's mark this response as pending.
    if (req.params_urlname in pendingResponse) {
        pendingResponse[req.params_urlname].push(res);
        return;
    }
    // The screenshot needs to be generated now. Let's mark the future response as pending.
    req.params_urlname[req.params_urlname] = [];

    // This will take very long time
    generateScreenshot(req.params_urlname, function(pngData) {

        cache.set(req.params_urlname, pngData, LONG_TIME);
        res.send(cached);

        // Let's respond all the pending responses with the PNG data as well.
        for (var i in pendingResponse[req.params_urlname]) {
            var pRes = pendingResponse[req.params_urlname][i];
            pRes.send(cached);
        }

        // No longer mark the future responses as pending.
        delete pendingResponse[req.params_urlname];
    });
});

这个解决方案有效。但是,我认为这个解决方案很脏,因为它根本不可重用。另外,我认为这可能会导致资源泄漏。有更好的解决方案/库吗?

node.js caching concurrency
2个回答
0
投票

这是一个概念验证服务器,使用 memoizee 包进行结果缓存(不仅消除了缓存正在进行的计算的必要性,而且还允许完全删除“缓存”):

var express = require('express');
var memoize = require('memoizee');

function longComputation(urlName, cb) {
  console.log('called for ' + urlName);
  setTimeout(function () {
    console.log('done for ' + urlName);
    cb();
  }, 5000);
}

var memoizedLongComputation = memoize(longComputation, {async: true, maxAge: 20000});

var app = express();
app.get('/hang/:urlname', function (req, res, next) {
  memoizedLongComputation(req.params.urlname, function () {
    res.send('hang over');
  });
});

app.listen(3000);

这里我们让结果缓存20秒。

当我启动服务器然后在 shell 中运行时

for i in `seq 1 10`; do curl http://localhost:3000/hang/url1; done

(或者只是打开几个浏览器选项卡,然后快速将它们全部导航到 http://localhost:3000/hang/url1),我在控制台中看到一个

"called for url1"
,并在 5 秒内看到一条
"done for url1"
消息,仅表示打了一个“真正的”
longComputation
电话。如果我在不久后(不到 20 秒)重复它,则不会有其他消息,并且结果会立即返回,因为它们已被缓存。如果我稍后(超过 20 秒)重复该命令,则又只有一次调用。


0
投票

请求合并


正如您所指出的,您所描述的问题的核心是

换句话说,重函数应该只执行一次来生成缓存。

为了实现这种行为,您可以阅读已知的设计模式Request Coalescing,它可以准确地实现此行为。

添加对 Discord 开发团队制作的博客的参考,该博客在“数据服务服务数据”部分下进一步深入了解详细信息此处

© www.soinside.com 2019 - 2024. All rights reserved.