MongoDB并发问题

10

我有一个与Facebook发送好友请求类似的应用场景。

当用户A向用户B发送好友请求时,内部会创建一个新的好友请求文档。稍后,当用户B也想向A发送好友请求时,系统将发现已存在一个好友请求文档,因此他们应该成为彼此的好友,不会创建新的好友请求文档。

我正在尝试解决这种情况:当用户A和用户B同时向对方发送好友请求时,将创建2个好友请求文档,导致行为不确定...

谢谢你的建议...非常感谢!

编辑: 一些人建议使用请求队列来解决这个问题;然而, 我对使用队列感到困惑,因为我认为它会使我的rest api端点按顺序处理请求。通过使用队列,我不会失去多线程的所有好处吗?如果我的服务有数百万个请求排队等待逐个执行,那么由于这个问题会变得有多糟糕。是否有人看到类似的生产问题?

3个回答

4

我有一个类似的客户情况,数据库中存在并发写入。我实现的方法是使用队列服务。

Create a request in the queue rather than writing in the database, a separate reader will 
read one message from the queue at a time and check if it is valid to write it to 
database, write only if there is no previous request.

你可以实现自己的队列,也可以使用像AWS-SQS、rabbitmq、MSMQ等服务。


这难道不意味着我的服务器将不再并行处理请求,这意味着它的性能不如以前了吗? - user1955934
是的,这似乎是一个边缘情况,但我对响应速度有所怀疑。如果您以其他方式解决了问题,请分享您的实现 :) - Harshal Bulsara
为什么要同时拥有A->B和B->A的单个项目呢?据我所知,我可以使用这两个项目,除非只有一个是绝对必要的。这种复合对象的用例是什么?否则,您可以选择以前建议的东西(队列)和一种分片之间的混入,这意味着您有几个队列,以便仍然可以使用并行性。例如,您可以设置8个队列,每个用户组为8个(例如按id模数分组)。 - SCO
1
@user1955934 不需要多个队列,一个队列就可以解决问题。 - Harshal Bulsara
@Kickaha,我并不是说使用同步块是解决方案,使用队列肯定会解决这个问题,但是我不能接受它,因为它对我的目的来说不可扩展。即使使用多个工作线程来处理队列中的请求,仍然会有相同的问题。我需要一个可以并行处理请求而没有问题的解决方案。这就好像我在问如何解决多线程问题,却被建议使用单线程作为解决方案... :( - user1955934
显示剩余6条评论

4

// 针对您的情况

  1. 在mongodb中,对单个文档的写操作是原子的。
  2. mongodb具有唯一索引的功能。

因此,如果您在插入带有A和B人名的文档时,在执行插入之前为两者创建唯一索引(例如通过字典顺序排序名称创建“A_B”),则您将固有地只能插入该文档的一个实例。

// 通用

我们想要的实质上是事务,但由于mongodb目前不支持此类操作,因此有一些技巧可以实现:

  1. 2阶段提交: https://docs.mongodb.org/v3.0/tutorial/perform-two-phase-commits/

  2. 使用外部源来维护标志,例如使用支持事务方式插入/比较和交换的memcache。


为了处理方向,可以添加另一个字段,即requestFrom和requestTo。唯一索引应该是完全不同的字段,用于标记A和B之间的关系(在A和B之间是唯一且仅出现一次)。 - Manjinder Aulakh
补充上面的评论,让我解释一下为什么我认为这是一个事务问题: 正如你所提到的,“主要问题是根据条件知道要创建哪个文档”,这是一个非常简单的问题,可以通过在代码中进行简单的检查来解决,但是“在检查完成后,这个条件可能不再成立”,这个条件将会改变的原因是没有办法知道在读取时是否有另一个线程正在写入,如果能够在文档上获取读/写锁,这个问题就可以得到解决。这本质上就是事务。 - Manjinder Aulakh
说了这么多,我建议的解决方案并不基于事务,而是基于 MongoDB 的属性(唯一索引),这样更加简单,无需使用外部队列或外部标志。您只需要处理可能在后续提交时出现的“重复 ID”异常即可。 - Manjinder Aulakh
我的friendRequest对象中有requestFrom和requestTo字段。但是这不能是唯一的,因为requestFrom A和requestTo B与requestFrom B和requestTo A不同。如果reqFrom和reqTo字段不同,我应该如何创建唯一索引字段?我认为这不是一个事务问题,因为按定义事务是我们应该以原子方式完成操作的方式,即2个写操作,如果一个写操作失败,则两个都将失败。在这种情况下,我只进行插入,基于读取操作... - user1955934
这是在将ID设置为"A_B"之前,可以对A、B进行排序的地方。因此,无论是A->B还是B->A,它们总是会变成"A_B"。 - Manjinder Aulakh
显示剩余4条评论

1
如果您在前端使用系统调用方法,则应该从数据库向前端发出一个请求,当某个用户喜欢时,我向您发送请求,然后在一秒钟内,数据库向您发送一个系统调用,您的前端代码立即更正按钮文本,例如 "添加好友" 变为 "待处理请求"。 否则,如果您只是设置数据库,则只需进行系统调用,当朋友请求到达或者像您所说的创建文档时将其发送到UI,进一步的处理将由UI开发人员处理。 谢谢。 如果您不喜欢答案,那么我很抱歉,但请不要因为我是Stack Overflow社区中的新成员而对我进行负面评价。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接