如何使用 MongoDB 建模“点赞”投票系统

Posted

技术标签:

【中文标题】如何使用 MongoDB 建模“点赞”投票系统【英文标题】:How to Model a "likes" voting system with MongoDB 【发布时间】:2018-02-11 08:03:00 【问题描述】:

目前我正在开发一个移动应用程序。基本上人们可以发布他们的照片,而追随者可以像 Instagram 一样喜欢这些照片。我使用 mongodb 作为数据库。就像 instagram 一样,一张照片可能会有很多点赞。因此,使用带有索引的单个“喜欢”的文档似乎是不合理的,因为它会浪费大量内存。但是,我希望用户快速添加一个赞。所以我的问题是如何为“喜欢”建模?基本上数据模型与 instagram 非常相似,但使用的是 Mongodb。

【问题讨论】:

您可以为每个指向照片的文档设置一个likes 字段。并使用$inc 运算符以原子方式更新每个文档的字段。但是,如果您可以发布您当前的文档结构并正确地重新表述您的要求,您会得到很好的答案。 添加喜欢将非常容易和快速,您可以将所有需要的数据直接传递到服务器,然后执行一个查询以直接插入数据库。但是,您将希望缓存和汇总点赞数,因为计算这些点赞数会很麻烦。包括 instagram 在内的大多数网站都使用计数器,例如 @BatScream 说使用 $inc(或他们使用的技术中存在的任何东西)来缓存喜欢的存在,以便轻松说出某物有多少喜欢 【参考方案1】:

无论您如何构建整个文档,您基本上都需要两件事。这基本上是一个“计数”和已经发布“喜欢”的人的“列表”的属性,以确保没有提交重复。这是一个基本结构:

 
    "_id": ObjectId("54bb201aa3a0f26f885be2a3")
    "photo": "imagename.png",
    "likeCount": 0
    "likes": []

无论如何,您的“照片帖子”和您想要的任何信息都有一个唯一的“_id”,然后是提到的其他字段。这里的“likes”属性是一个数组,它将保存系统中“user”对象的唯一“_id”值。因此,每个“用户”在某个地方都有自己的唯一标识符,无论是在本地存储中还是在 OpenId 或其他地方,但都是唯一标识符。以ObjectId 为例。

当有人向帖子提交“赞”时,您要发出以下更新声明:

db.photos.update(
     
        "_id": ObjectId("54bb201aa3a0f26f885be2a3"), 
        "likes":  "$ne": ObjectId("54bb2244a3a0f26f885be2a4") 
    ,
    
        "$inc":  "likeCount": 1 ,
        "$push":  "likes": ObjectId("54bb2244a3a0f26f885be2a4") 
    
)

现在$inc 操作将“likeCount”的值增加指定的数字,因此增加1。$push 操作将用户的唯一标识符添加到文档中的数组以供将来参考。

这里最重要的事情是记录那些投票的用户以及语句的“查询”部分发生的事情。除了通过它自己唯一的“_id”选择要更新的文档之外,另一个重要的事情是检查“likes”数组以确保当前投票用户不在其中。

对于相反的情况或“删除”“喜欢”也是如此:

db.photos.update(
     
        "_id": ObjectId("54bb201aa3a0f26f885be2a3"), 
        "likes": ObjectId("54bb2244a3a0f26f885be2a4")
    ,
    
        "$inc":  "likeCount": -1 ,
        "$pull":  "likes": ObjectId("54bb2244a3a0f26f885be2a4") 
    
)

这里最重要的是使用查询条件来确保如果所有条件都不满足,则不会触及任何文档。因此,如果用户已经投票,则计数不会增加;如果更新时他们的投票实际上不再存在,则计数不会减少。

当然,在应用程序的任何其他部分中读取文档中包含数百个条目的数组是不切实际的。但是 MongoDB 也有一个非常标准的方法来处理它:

db.photos.find(
     
        "_id": ObjectId("54bb201aa3a0f26f885be2a3"), 
    ,
     
       "photo": 1
       "likeCount": 1,
       "likes":  
          "$elemMatch":  "$eq": ObjectId("54bb2244a3a0f26f885be2a4") 
       
    
)

$elemMatch 在投影中的这种用法只会在当前用户存在时返回当前用户,或者只是在他们不存在的情况下返回一个空白数组。这允许您的应用程序逻辑的其余部分知道当前用户是否已经投票。

这是基本技术,可能对您有用,但您应该注意嵌入式数组不应无限扩展,并且 BSON 文档也有 16MB 的硬性限制。所以这个概念是合理的,但如果您期望您的内容获得 1000 次“点赞”,则不能单独使用它。有一个称为“桶”的概念,在此示例中针对Hybrid Schema design 进行了一些详细讨论,它允许一种解决方案来存储大量“喜欢”。您可以将其与此处的基本概念一起使用,作为批量执行此操作的一种方式。

【讨论】:

不错的答案,很抱歉提出这个问题,但是您对实施这样的解决方案有什么想法(而不是使用子文档来保持喜欢或投票)***.com/questions/26914380/… @Disposer 这里的一般想法是尽可能简单地检查某人是否投票,并在没有汇总的情况下读回总投票数,或者至少通过拆分为尽可能少的文档可能的。其他模型要么依赖实时聚合,要么在更新中不是原子的。写得快,读得也快。对于通常是您想要的高活动项目。 @Neil Lunn。感谢您的回答。实际上我的数据结构和你的非常相似,我打算使用分桶设计。我想知道使用 $elemMatch 运算符查找大量喜欢的桶的性能有多好。假设一张照片有 30,000 个赞,我有 300 个桶,每个桶包含 1000 个赞。知道当前用户是否已经投票是否有效?我也对你提到的“其他模型要么依赖实时聚合”感兴趣,你能解释一下,以便我评估更多选项吗? @user2914635 你现在问的是另一个问题,但即便如此,它也是一个相当广泛的问题。如果您想了解不同的技术并且不介意阅读一些代码,那么您可以查看hvdf 和socialite 源。甚至还有一些来自达人的演讲,比如this one。还要考虑到获得 300,000 个赞将是例外,而不是规则。 @Neil Lunn。基本上,分桶设计的优点是它可以节省内存并易于检索到显示给最终用户的喜欢,而分桶设计的缺点是它使插入更昂贵。将每个点赞视为单个文档的好处是它可以有效地插入点赞和检索点赞也具有合理的性能,但会浪费大量内存。我说的对吗?据我所知,Instagram 通常每个帖子都有很多赞。假设我也有很多喜欢的帖子。我应该采用哪种数据模型?需要您的建议,在此先感谢

以上是关于如何使用 MongoDB 建模“点赞”投票系统的主要内容,如果未能解决你的问题,请参考以下文章

如何确定帖子是不是被 Mongoose 的用户点赞

如何在 MongoDB 中建模关系? [复制]

如何记住匿名投票

在 MongoDB 中,如何根据对象的值对对象进行排序?

使用 mongoDB 启动 H2 DB

如何在 MongoDb 中为这样的链接帐户建模?