【问题标题】:Node.js: Multiple very heavy requests at the same time, single response to all requestsNode.js:同时多个非常重的请求,对所有请求的单一响应
【发布时间】:2015-08-14 10:39:37
【问题描述】:

很抱歉,我想不出更好的标题。

我总是遇到这个问题(在 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 个对该 URL 的 HTTP 请求!这意味着您的服务器必须生成 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];
    });
});

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

【问题讨论】:

  • 我不太擅长 javascript,但如果您的 'generate_screenshot' 是同步的,那么 server.get(...) 的回调将一个接一个地运行,这不是真的吗所以你的第一种方法很好吗?我还怀疑您更愿意让它异步,并且在这种情况下,您可能会对“memoize”模块感兴趣以实现您想要的(github.com/medikoo/memoize)。
  • @starikoff 是的,generate_screenshot 应该是异步的。我的错在这里。 memoize 模块如何只允许同时执行 1 个 generate_screenshot() 函数?
  • 我无法阅读这种复杂的 javascript。根据我的判断('ext/async.js')和我的期望(我过去在 Java 中为 GWT 项目手动实现了类似的东西),我的狂猜测是以下:
  • 该模块有一个用于“运行”计算的全局缓存。计算是一个“回调”对象,它可以收集其他回调,并在调用时将它们全部运行并从缓存中擦除自身。通过对异步函数 f 应用“memoize”,可以创建具有相同签名的新异步函数。调用时,它:1)尝试从缓存中获取此函数+参数的计算,2)如果没有,则创建它并添加到缓存中,3)在任何情况下都将回调添加到计算中,4)如果它创建了一个新的计算,则运行原始函数 f 并将该计算作为回调。
  • 您的缓存可以包含图像或承诺。因此,创建图像的第一个请求会在缓存中放置一个承诺。立即进入的后续请求将检查缓存并找到承诺,他们只会将.then() 处理程序附加到该承诺。当第一个请求完成图像制作时,它会解析承诺,然后将缓存中的承诺替换为实际的图像数据。

标签: node.js caching concurrency


【解决方案1】:

这是一个使用 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 秒)重复该命令,则再次只有一个调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-28
    • 2012-08-04
    • 2011-11-13
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2021-08-06
    • 1970-01-01
    相关资源
    最近更新 更多