5

我正在针对分片mongos集群上的实例对大型集合运行重复检测 mapreduce 操作,我预计该操作需要超过 10 分钟:

m = function () {
    emit(this.fieldForDupCheck, 1);
}
r = function (k, vals) {
    return Array.sum(vals);
}
res = db.Collection.mapReduce(m, r, { out : "dups" });

在处理大约 10 分钟后,运行它会给我以下错误:

uncaught exception: map reduce failed:{
"ok" : 0,
"errmsg" : "MR post processing failed: { result: "dups", errmsg: "exception: getMore: cursor didn't exist on server, possible restart or timeout?", code: 13127, ok: 0.0 }"
}

我尝试通过在 mapReduce 调用上使用来添加 noTimeout 选项.addOption(DBQuery.Option.noTimeout),但这会导致 shell 中出现 JS 错误Object [object Object] has no method 'addOption'

如何避免长时间运行的 mapreduce 操作出现光标超时?

4

1 回答 1

7

您没有提到您使用的是哪个版本的 MongoDB,但无论如何,解决方案将与此处介绍的类似。我将在 Ubuntu 13.04 附带的 2.2.4 之上进行演示。

这样做的问题确实是将选项注入游标。这就是addOption生活的地方:

> var cursor = db.test.find()
> cursor.addOption
function (option) {
    this._options |= option;
    return this;
}

让我们看看是如何mapReduce定义的:

> db.test.mapReduce
function (map, reduce, optionsOrOutString) {
    var c = {mapreduce:this._shortName, map:map, reduce:reduce};
    ...
    var raw = this._db.runCommand(c);
    ...
    return new MapReduceResult(this._db, raw);
}

因此,它构建了一个文档以通过runCommand. 让我们进一步研究一下:

> db.runCommand
function (obj) {
    if (typeof obj == "string") {
        var n = {};
        n[obj] = 1;
        obj = n;
    }
    return this.getCollection("$cmd").findOne(obj);
}

所以命令是通过findOne. 让我们研究一下:

> db.test.findOne
function (query, fields, options) {
    var cursor = this._mongo.find(this._fullName, this._massageObject(query) || {}, fields, -1, 0, 0, options || this.getQueryOptions());
    if (!cursor.hasNext()) {
        return null;
    }
    var ret = cursor.next();
    ...
    return ret;
}

啊,这里有一些有趣的东西。光标正在使用来自参数的标志进行初始化options,不幸的是,这对您的情况没有帮助,因为runCommand它未设置,但它与getQueryOptions()来自集合的 进行 OR 运算。让我们研究一下:

> db.collection.getQueryOptions
function () {
    var options = 0;
    if (this.getSlaveOk()) {
        options |= 4;
    }
    return options;
}

哎。。不好说。所以我们无法访问游标,也无法通过非黑客手段将查询选项注入到执行的命令中。

好吧,但是我们已经了解了很多关于 map reduce 命令实际上是如何通过该过程传递到服务器的。它只是针对数据库中特定集合查询的文档。这意味着我们可以构建相同的查询并自己运行它,但需要提供任何必要的标志。

我不会讨论构建整个 MongoDB 命令并为您设置结果的麻烦,但我只会通过使用isMaster命令向您展示它实际上是有效的。

这是没有任何标志运行的命令:

> db.getCollection("$cmd").findOne({isMaster: 1}).ismaster
true

要查看效果上的差异,我们将 tcpdump 与数据库的通信。我们可以在有线协议文档中看到,相关标志位于集合名称之前,以 32 位整数形式存在,因此很容易发现转储的相关部分:

    .                  vvvvvvvvv
    0x0040:  d407 0000 0000 0000 7465 7374 2e24 636d  ........test.$cm
    0x0050:  6400 0000 0000 ffff ffff 1700 0000 0169  d..............i

好的。我们可以看到四个字节被归零,就在集合名称之前。

现在,让我们在提供一些标志的同时做同样的事情。我们从上面的调试部分了解到,查询标志可以作为 的第三个选项提供findOne,所以让我们这样做:

> db.getCollection("$cmd").findOne({isMaster: 1}, undefined, 0xBEEF).ismaster
true

并查看转储:

    .                  vvvvvvvvv
    0x0040:  d407 0000 efbe 0000 7465 7374 2e24 636d  ........test.$cm
    0x0050:  6400 0000 0000 ffff ffff 1700 0000 0169  d..............i

嘿,我们的标志被传递到了应该的地方,我们还可以看到它被反转了,这意味着字节被编码为 little-endian,与docs匹配。

因此,这意味着您可以提供标志DBQuery.Option.noTimeout作为 的第三个选项,并按照文档findOne中描述的方式手动编码 map-reduce 命令,方法与我们对 的操作类似,您将得到您想要的。isMaster

于 2013-08-30T21:30:13.007 回答