【问题标题】:NodeJS - Force users to wait until global event is completedNodeJS - 强制用户等到全局事件完成
【发布时间】:2012-09-19 22:24:58
【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

    标签: node.js events asynchronous


    【解决方案1】:

    这可以通过使用 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,并且每个请求都会在结果可用后立即得到响应。

    【讨论】:

    • 我知道这是一篇旧帖子,但这几乎是我最终要做的。
    【解决方案2】:

    避免此事件(顺便说一下,它被称为“雷击群问题”)的最简单方法是不让任何用户运行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]
    

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

    【讨论】:

    • 我明白你的意思,但是,我宁愿不向用户返回“错误”消息,因为数据的获取实际上非常快。如果我们让请求最多等待 10 秒,我们将准备好缓存。无论用户是否触发了这个事件,我们可能仍然会遇到用户在服务器中没有准备好数据的问题,所以我宁愿让用户等待
    • 刚刚看到您的更新,是的,这就是我正在做的,提供陈旧数据并更新缓存,但我当然看到了同样的事情,多个用户触发缓存刷新
    • 好的,我第一次尝试解决这个问题与您的建议类似,但是,我似乎在某处读到使用全局变量不是很好的风格,但在这种情况下,它可能是唯一的办法,非常感谢您的帮助
    • @user1112585 - 如果可以,您不想使用全局变量 - 您的 jobLockscache 理想情况下将存在于单独的 进程 中(Redis , Memcached, 数据库 [如果应用程序流量低] 等)您的工作进程将与您的 Web 服务器分开。在理论上,所有这些进程都可以在地理上不同的数据中心中完全独立的机器上运行,而不会出现问题(实际上,它们通常位于同一个数据中心,对于小型应用程序来说,它们通常位于同一台机器上)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 2017-03-27
    • 2020-09-18
    • 1970-01-01
    相关资源
    最近更新 更多