使用MongoDB + NodeJS避免竞争条件和饥饿

时间:2018-02-11 18:50:22

标签: node.js mongodb race-condition

我有一个代理管理池,负责存储,检查和检索代理,以便它们可以用于Web请求。

async getNextAvailableProxy() {
    while(true) {
        var sleepTime = global.Settings.ProxyPool.ProxySleepTimeMS;
        var availableProxies = await this.data.find(PROXY_COLLECTION, {
            $query: {
                Enabled: true,
                InUse: false,
                LastUsed: { $lte: new Date(new Date() - sleepTime) }
            },
            $orderby: { ResponseTime: 1 }
        });

        if (availableProxies.length <= 0) {
            var nextAvailable = await this.data.findOne(PROXY_COLLECTION, {
                $query: { Enabled: true, InUse: false },
                $orderby: { LastUsed: -1 }
            });

            if (!nextAvailable) {
                await Utils.sleep(100);
                console.log('No proxies available, sleeping');
                continue;
            }

            sleepTime = sleepTime - (new Date() - nextAvailable.LastUsed)
            if (sleepTime > 0)
                await Utils.sleep(sleepTime);

            continue;
        }

        var selectedProxy = availableProxies[0];
        selectedProxy.InUse = true;
        await this.data.save(PROXY_COLLECTION, selectedProxy);

        return selectedProxy;
    }
}

值得注意的是,我的findsave版本是针对NodeJS的MongoDB驱动程序的包装。

值得注意的是,Utils.sleep()是使用setTimeout执行异步睡眠的承诺。

现在,我知道由于NodeJS是单线程的,因此不会出现竞争条件。但是,当使用多个快速查询数据库的隔离对象时,情况根本不是这样。

如果我有五个对象ProxyPool的实例,并且它们都在短时间内调用getNextAvailableProxy(),它们将从数据库中获取相同的代理,因为一个实例具有在另一个实例保存了InUse标记之前已经启动了查询,只留下了n - ProxyPool个实例都检索了保存完全代理。

如何以异步方式解决这个问题?

1 个答案:

答案 0 :(得分:1)

老实说,根据你的帖子很难说出为什么会出现问题。虽然碰撞可能会发生,但在我看来应该是不够重要的,除非使用代理是一个非常长时间运行的操作(因此给定的代理很多)。

也就是说,我也不会在每个请求上查找代理。相反,我可能让每个工作人员在启动时或间隔(可能每小时一次)获取一个代理池,然后在内部管理(内存中)它可用的代理。

用于确定给定给定工作人员的代理的算法可以非常灵活,并且不太可能发生冲突,因为每个节点实例都是单线程的,它不会两次分配相同的代理。

风险在于您可能会遇到某个特定工人用完代理的地方。这也是你需要处理的事情,但是因为你(理论上)会让你的工人以某种方式负载平衡,如果你到达那个地方,你可能会用尽代理并且必须尽快发出Too Busy回复。

最后,当您点击数据库获取可用代理列表时,您应该使用findAndModify()或类似内容一次性获取和更新文档,这样当您从数据库中取出一个时告诉数据库它不可用,而不是先等待Web服务器上的处理。