【发布时间】: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