0

在习惯了 SQL 之后,我遇到了 mongoDB 的这个问题。首先,我使用的是猫鼬。

现在,问题。我有一个名为User.

var UserSchema = new Schema ({
    id : ObjectId,
    name : {type : String, trim : true, required : true},
    email: {type:String, trim:true, required: true, index: { unique: true }},
    password: {type:String, required: true, set: passwordToMD5},
    age: {type:Number, min: 18, required: true, default: 18},
    gender: {type: Number, default:0, required: true},

    height: {type: Number, default:180, min: 140, max: 220},
    _eye_color: {type: ObjectId, default: null},
    location: {
            lon: {type: Number, default: 0},
            lat: {type: Number, default: 0}
    },
    status: {type:Number, required: true, default:0}
    },{
        toObject: { virtuals: true },
        toJSON: { virtuals: true },
        collection:"user"});

现在我需要从这个集合中选择所有用户并按特殊属性(比如“排名”)对它们进行排序。这个排名是根据他们与某个点的距离、年龄与给定年龄的比较等,用一定的逻辑计算出来的……

所以现在我想知道如何选择这个等级然后在排序中使用它?我曾尝试使用 virtuals,它们可以方便地计算附加信息,但不幸的是,无法find()按虚拟字段对结果进行排序。当然我可以在虚拟中计算这个排名,然后选择所有记录,然后在回调中做一些javascript。但在这种情况下,当我选择所有用户然后排序然后限制时,javascript 部分可能需要太长时间......我正在考虑使用mapreduce,但我不确定它会做我想要的。如果我的任务可以在mongoDB/mongoose中完成,有人可以给我一个提示吗?

编辑 1

我也尝试过使用聚合框架,起初它似乎是具有该$project能力的最佳解决方案。但是后来,当我需要进行排名计算时,我发现聚合不支持很多数学函数,如sin,cossqrt。而且在投影中也不可能使用预定义的常用 javascript 函数。我的意思是,该函数被调用,但我无法将当前记录字段传递给它。

{$project: {
  distance_from_user: mUtils.getDistance(point, this.location)
}

在函数内部,第二个属性是“未定义的”。

所以我想用聚合框架来做我的排名计算是不可能的。

编辑 2 好的,我知道每个人都告诉我不要使用 mapreduce,因为它不适合实时查询,但由于我不能使用聚合,我想我会尝试 mapreduce。所以假设我有这张地图减少。

function map() {
            emit(1, // Or put a GROUP BY key here
                {name: this.name, // the field you want stats for
                    age: this.age,
                    lat: this.location.lat,
                    lon: this.location.lon,
                    distance:0,
                    rank:0

                });
        }

        function reduce(key, values) {


            return val;
        }

        function finalize(key, value){

            return value;
        }


        var command = {'mapreduce': "user", 'map': map.toString(), 'reduce': reduce.toString(), query:{$and: [{gender: user_params.gender}, {_id: {$ne: current_user_id}}]}, 'out': {inline:1}};

        mongoose.connection.db.executeDbCommand(command, function(error, result){
            if(error) {
                log(error);
                return;
            }
            log(result);
            return;
        });

我应该在 reduce(或者可能更改 map)中写什么来计算每个用户的排名?

4

3 回答 3

1

唯一真正的解决方案是计算每个文档的排名并将其存储在文档中。由于只要文档中的值保持不变,该值就会保持不变,您可以在更新影响它的字段时简单地计算该值。

Map/reduce 当然不是一个好的解决方案,任何其他类型的聚合也不是。如果您使用 MongoDB,则预先计算您的排名并将其与文档一起存储是唯一可以扩展的选项。

于 2012-11-21T13:49:14.037 回答
1

您知道这样的事情需要的计算量 - 如果您每次用户登录时都这样做,那么当很多人会在更短的时间内登录时,您会遇到有趣的负载峰值 - 以及您的页面(界面)将受到资源的严重限制(这不好)。
我会向您推荐一些不同的东西 - 保持每个登录用户的排名并定期更新它们:保持“短会话”和“长会话”(长会话 - 您在网络浏览器中使用的会话和短 - “在线,当前使用该站点”)并定期为“短期活跃”用户生成排名,而很少为长时间会话中的登录用户生成排名。每五分钟一次。
在这种情况下,您可以使用 mapredurce - 您的 map 函数应该只发出计算给定用户排名所需的数据(如年龄、纬度、经度,无论您需要什么)和测试用户的结果(排名)(发出它是空的)。对于 reduce 功能,您需要查看使用 mapreduce 进行排序(这在很大程度上取决于您创建排名的方式) - 您还需要计算其他用户的排名(或某种子值)。

于 2012-11-21T18:12:54.763 回答
0

它看起来像是MongoDB + Hadoop的一个很好的用例。

演示文稿展示了这种组合的一些可能性。

于 2012-11-21T14:29:51.800 回答