【问题标题】:Mongoose data flow猫鼬数据流
【发布时间】:2018-11-27 21:57:12
【问题描述】:

我构建了一个简单的 MERN 应用,用户可以在其中对电话号码进行评分。用户只需填写电话号码,选择评级(1 - 5 星评级),他们的城市和短文本。该应用程序具有过滤和排序选项的搜索功能。这一切都足够好 ATM 但我认为当多个并发用户使用该网站时它可能会中断,因为我在提交评级 (messageSchema) 后更新了电话号码模型 (mobileSchema) - 使用 Mongoose 中间件 (post hooks)。

例如,我需要计算电话号码的评分数 (messagesCount)。我为此使用Message.countDocuments({ mobile: mobile._id })。但是,我还需要更新电话号码的其他属性(mobileSchema - lastMessageDate、globalRating、averageRating),以便操作需要一些时间。我相信当 2 个用户同时提交评分时,评分数量可能不正确 - 它会将评分数量 (messagesCount) 增加 1 而不是 2。

有没有更好的方法?可以在前一个 post hook 完成后触发 post hook 吗?

示例代码:

const mobileSchema = new Schema({
    number: { type: String, required: true },
    plan: { type: String, required: true },
    date: { type: Date, default: Date.now, required: true, index: 1 },
    messagesCount: { type: Number, default: 0, index: 1 },
    lastMessageDate: { type: Date, index: 1 },
    // normal mean
    globalRating: { type: Number, default: 0, index: 1 },
    // weighted mean
    averageRating: { type: Number, default: 0, index: 1 }
});

const messageSchema = new Schema({
    comment: { type: String, required: true },
    city: { type: Schema.Types.ObjectId, ref: 'City', required: true, index: 1 },
    rating: { type: Number, required: true, index: 1 },
    date: { type: Date, default: Date.now, required: true, index: 1 },
    mobile: { type: Schema.Types.ObjectId, ref: 'Mobile', required: true },
    user: { type: Schema.Types.ObjectId, ref: 'User', required: true }
});

messageSchema.post('save', function (message, next) {
    const messageModel = this.constructor;
    return updateMobile(messageModel, message, next, 1);
});

const updateMobile = (messageModel, message, next, addMessage) => {
    const { _id } = message.mobile;
    const cityId = message.city._id;
    const lastMessageDate = message.date;
    let mobile;
    hooks.get(Mobile, { _id })
        .then(mobileRes => {
            mobile = mobileRes;
            return Message.countDocuments({ mobile: mobile._id })
        })
        .then(messagesCount => {
            if (messagesCount <= 0) {
                const deleteMobile = Mobile.findOneAndDelete({ _id: mobile._id })
                const deleteSeen = SeenMobile.findOneAndDelete({ mobile: mobile._id, user: message.user._id })
                const cityMobile = updateCityMobile(messageModel, mobile, cityId)
                Promise.all([deleteMobile, deleteSeen, cityMobile])
                    .then(() => {
                        return next();
                    })
                    .catch((err) => {
                        console.log(err);
                        return next();
                    })
            }
            else {
                if (addMessage === -1) lastMessageDate = mobile.lastMessageDate;
                const ratings = hooks.updateGlobalRating(mobile, messageModel)
                    .then(() => hooks.updateAverageRating(mobile, messageModel))
                    .then(() => {
                        return new Promise((resolve, reject) => {
                            mobile.set({
                                messagesCount,
                                lastMessageDate
                            });
                            mobile.save((err, mobile) => {
                                if (err) return reject(err);
                                resolve();
                            });
                        })
                    })
                const cityMobile = updateCityMobile(messageModel, mobile, cityId)
                Promise.all([ratings, cityMobile])
                    .then(([ratings, cityMobile]) => {
                        return next();
                    })
                    .catch(err => console.log(err))
            }
        })
        .catch(err => {
            console.log(err);
        })
}

【问题讨论】:

  • 不直接与猫鼬相关,但here 是关于写操作的讨论。根据该信息,我会说写操作完成后会触发 post hook,但我没有检查 mongoose 文档。

标签: javascript mongodb express mongoose


【解决方案1】:

我认为您的方法总是会遇到异步问题。我不相信你可以“同步”这些钩子。似乎违背了关于 MongoDB 的一切。但是,在较高级别上,您可能会更成功地在运行时获取总计/摘要,而不是尝试使它们始终保持同步。例如,如果您需要给定移动设备的消息总数,为什么不:

Messages.find({mobile: mobile._id})

然后计算结果?这将节省您存储摘要并保持更新的时间。但是,我也认为您当前的方法可以工作,但您可能需要废弃“countDocuments”。对异步更友好的东西,例如:

Mobile.aggregation([
    { $match: { _id: mobile._id } },
    { $add: [ "$mobile.messagesCount", 1 ] }
]);

最终,我认为如果您将消息作为数组存储在 Mobile 中,您的设计会得到加强,因此您可以将消息推送到上面。但是要直接回答这个问题,聚合应该保持一切整洁。

【讨论】:

  • “在运行时获取总计/摘要”:我认为这对性能不利。我必须计算来自客户端的每个请求的所有属性(messagesCount、lastMessageDate、globalRating、averageRating ...)。如此简单的过滤和排序将花费太长时间。 “将消息作为数组存储在 Mobile 中”:恐怕它可能会增长到超过 MongoDB 文档的最大大小 - 16 MB。
  • 很公平。第二点很好,但是关于第一点,您是否考虑过使用像 Redis 这样的缓存?在数据库中缓存似乎很费力。
【解决方案2】:

我找到了这个答案:Locking a document in MongoDB

我将在 post hook 中计算我需要的所有值(messagesCount、globalRating 等),然后在最终的 findOneAndUpdate 操作期间检查移动文档是否具有相同的 __v(版本)值(因为此操作会锁定文档并可以增加 __v)。如果它有不同的 __v 那么我会再次调用 post hook 以确保它会计算正确的值。

【讨论】:

    【解决方案3】:

    首先我们需要在这里修复一些数据库结构

    移动架构

    const mobileSchema = new Schema({
      number: { type: String, required: true },
      plan: { type: String, required: true },
      date: { type: Date, default: Date.now, required: true, index: 1 },
      //messagesCount: { type: Number, default: 0, index: 1 },
      //lastMessageDate: { type: Date, index: 1 },
      // normal mean
      //globalRating: { type: Number, default: 0, index: 1 },
      // weighted mean
      //averageRating: { type: Number, default: 0, index: 1 }
    });
    

    消息架构

    const messageSchema = new Schema({
      comment: { type: String, required: true },
      city: { type: Schema.Types.ObjectId, ref: 'City', required: true, index: 1 },
      //rating: { type: Number, required: true, index: 1 },
      date: { type: Date, default: Date.now, required: true, index: 1 },
      mobile: { type: Schema.Types.ObjectId, ref: 'Mobile', required: true },
      user: { type: Schema.Types.ObjectId, ref: 'User', required: true }
    });
    

    评级系统(获取所有评级或将它们设为一组) (100个评分后的分子和分母很难阅读每一个)也可以检查手机

    const ratingSchema = new Schema({
      mobile: { type: String, required: true },
      commmentId:{type:String, required: true, index: 1}
      rate: { type: Number required: true,  },
      //rating: { type: Number, required: true, index: 1 },
      timestamp: { type: Date, default: Date.now, required: true, index: 1 }
      denominator:{ type: Number},
      numerator:{type:Number}
    });
    

    谢谢

    【讨论】:

      猜你喜欢
      • 2015-03-29
      • 2012-04-23
      • 2020-03-10
      • 2016-05-05
      • 2016-05-05
      • 2021-09-22
      • 1970-01-01
      • 1970-01-01
      • 2015-09-10
      相关资源
      最近更新 更多