1

我有一个节点服务器,它执行以下操作:

我在外部服务器中有一个 URL 列表,称为 URLServer。当用户访问我的节点服务器时,我的节点服务器向 URLServer 发出请求并获得一个包含 20 个 URL 的列表。一旦我们得到这 20 个 URL,我希望我的节点服务器去获取每个 URL 的标题,这意味着我将获取 URL 并创建一个 DOM,然后提取标题,我还会得到其他数据,所以这就是它必须完成的方式。完成此操作后,我希望将 URL 的标题和 URL 保存在内部存储器和/或数据库中。所以我有一个 URL 缓存和一个标题缓存(我不想一直获取 URL)。

我有这样的东西: if(URL-cache is empty) 从 URLServer 获取 URL 并缓存这些 URL

然后,我想检查每个 URL 以查看它们的标题是否在我的缓存中,所以我这样做:对于每个 URL,如果 title-cache [URL],则获取站点,创建 DOM,提取标题 + 其他数据并缓存

这对一个用户很有用,但是当我在服务器中尝试重负载时,服务器会挂起。我认为服务器挂起的原因如下:

用户 1 请求 - 空缓存 - 获取 URL 并在完成后获取 URL 的内容 用户 2 请求 - 缓存对于该用户来说仍然是空的,因为对用户 1 的请求尚未完成!!!因此,用户 2 再次强制获取 URL 及其各自的内容。用户 3 请求 - 用户 1 和用户 2 请求尚未完成,因此同样的问题...

因此,假设我需要获取 10 个 URL,而不是打开 10 个连接,每个 URL 一个然后缓存数据,如果我有 20 个用户同时访问服务器,我将打开 200 个连接(每个用户打开 10 个连接)。

如何阻止用户 X(其中 X>1)导致这些事件?我基本上希望服务器关闭一个门并要求每个用户等到它填充缓存,然后在填充这些门后打开门,有没有办法做到这一点?

4

2 回答 2

3

这可以通过使用 EventEmitter 类来完成。你设置了一个 EventEmitter

    var events = require('events');
    var eventEmitter = new events.EventEmitter();

然后你处理你的传入请求

    // here you check for url in cache with your own logic
    if(weHaveUrl){
      // Respond directly
    } else {
      // Add one time event watcher for that url
      eventEmitter.once('url-' + url, function(data){
        // We now have data so respond
      });
      // Initiate search
      searchUrl(url);
    }

并包装您的搜索功能以发出事件

    var urlSearchList = [];
    function searchUrl(url){
      // We check in case we are already looking for the data
      if(urlSearchList.indexOf(url) === -1){
        // Append url to list so we won't start a second search
        urlSearchList.push(url);

        // Your logic for searching url data
        // Once recieved we emit the event
        eventEmitter.emit('url-' + url);
        // And optionally remove from search array 
        //  if we want to repeat the search at some point
        urlSearchList.splice(urlSearchList.indexOf(url));
      }
    }

如果结果在缓存中,此方法将立即响应请求,或者让它们等待搜索结果然后返回结果。

由于我们会记录启动了哪些搜索,因此我们不会多次开始搜索相同的 url,并且每个请求都会在结果可用后立即得到响应。

于 2012-09-19T14:24:58.767 回答
1

避免此事件的最简单方法(顺便说一下,它被称为“雷群问题”)是不让任何用户运行fetchURLs代码。相反,如果缓存检查失败,则将作业添加到作业队列以刷新此数据。然后返回一条消息,内容为“我们很抱歉,我们现在没有该数据 - 请稍候,我们正在为您获取数据”。然后,您只需轮询您的端点以获取数据,一旦它在缓存中,您就已准备就绪并准备就绪。

为了防止 100 个用户将作业提交到队列中,将标志添加到另一个全局可用的数据结构(可能与您用于作业队列的相同,但不一定)。当您遇到缓存未命中时,请检查该缓存键的标志是否存在,如果不存在,请设置该标志并将作业提交到您的作业队列。在伪代码中:

if url not in cache:
    if url not in jobLocks:
        jobLocks.add(url)
        jobQueue.add("fetchURLs", data=url)

    return "Please wait while we fetch your data"

else:
    return cache[url]

当缓存中的数据过时时,您可以使用相同的过程来避免更新时出现雷鸣般的羊群。不是删除数据然后重新获取它,而是服务器陈旧数据并将作业放入队列中以更新缓存。

于 2012-09-19T02:24:51.590 回答