Php MongoDB:解决竞争条件的策略

Php MongoDB:解决竞争条件的策略,php,mongodb,Php,Mongodb,我们有一个场景,需要在站点模型下存储多个提要,如下所示: { id: site_id name: site_name feeds: [ { url: feed_url_1 date: feed_update_date_1 }, { url: feed_url_2 date: feed_update_date_2 }, ... ] } 由于feeds是一个数组,我们可以使用$set、$push

我们有一个场景,需要在站点模型下存储多个提要,如下所示:

{
  id: site_id
  name: site_name
  feeds: [
    {
      url: feed_url_1
      date: feed_update_date_1
    },
    {
      url: feed_url_2
      date: feed_update_date_2
    },
    ...
  ]
}
由于
feeds
是一个数组,我们可以使用
$set
$push
$addToSet
对其进行更新

2当我们的并发应用程序(队列)尝试更新同一站点模型时,可能会出现不同的竞争条件(写倾斜)

如果我们选择
$set
,并在客户端保护复制,那么如果两个队列正在写入同一个站点,则一个提要可能会丢失,顺序如下

Given a wordpress site, extract 2 feeds (RSS and ATOM), dispatch to Q1 and Q2.
Q1: load existing feed, check RSS feed is new
Q2: load existing feed, check ATOM feed is new
Q1: $set feeds => [RSS]
Q2: $set feeds => [ATOM]
现在RSS提要丢失了

如果我们选择
$push
$addToSet
,则可能会发生以下情况

User A added a site, putting RSS feed to Q1
User B added the same site, putting the same RSS feed to Q2
Q1: load existing feed, check RSS feed is new
Q2: load existing feed, check RSS feed is new
Q1: $push RSS
Q2: $push RSS
现在RSS提要已被复制

如果我们的数据模型只是
{url}
,那么
$addToSet
将防止重复提要。但不幸的是,情况并非如此,
date
属性可能有所不同。因此,
$addToSet
并不比
$push
安全多少

我们已经考虑了一些解决这个问题的可能方法,但是考虑到我们的日程安排很紧,没有一个是好的

  • 将提要从站点分离到自己的集合中,单独使用
    url
    进行保护,并相应地更改我们的模型和存储库

  • 首先在站点模型中插入部分
    {url}
    ,然后使用附加信息更新它们,这将使
    $addToSet
    可用,但可能会中断要求始终存在
    日期
    的其他队列(需要测试)

  • 让竞争条件按原样发生,
    $push
    首先推送提要,使用后台队列检测重复项,然后将其删除

  • (如果upsert使用位置查询,可能会有第四种解决方案,但据我所知,MongoDB v2.4还没有)


    所以我想知道是否有更好的替代方案来解决这种竞争状况。或者如果有一些最佳实践。

    您可能想看看mongodb的fork,它支持(除了一些其他有用的东西)

    您可以在更新选择器上使用GAD:

    alice(mongod-2.4.8) test> db.foo.save({_id: 12 })
    Updated 1 new record(s) in 1ms
    alice(mongod-2.4.8) test> db.foo.update({ _id: 12, "feeds.url" : {$ne: "baz"} }, 
        { $push :     { feeds : { url: "baz" } } } )
    Updated 1 existing record(s) in 1ms
    alice(mongod-2.4.8) test> db.foo.update({ _id: 12, "feeds.url" : {$ne: "baz"} },
        { $push : { feeds : { url: "baz" } } } )
    Updated 0 record(s) in 1ms
    alice(mongod-2.4.8) test> db.foo.find({_id: 12 })
    {
        "_id": 12,
        "feeds": [
            {
                "url": "baz"
            }
        ]
    }
    Fetched 1 record(s) in 1ms -- Index[_id_]
    

    你很可能是用错了商店。对于基于文档的数据库,您必须将唯一性信息存储为值的组合名称的一部分,以便不会发生重复。因此,您在示例中为RSS保留了一个子文档,为ATOM保留了一个子文档。你必须跳出框框,把你认为的只是标准工作中的竞争条件转变为不一致且最终重复的数据。@hakre thx,如果是这样,那么我们需要为每个提要url生成某种类型的slug,因为可能有多个RSS/ATOM提要。不幸的是,这确实需要对模型进行大量更改,但我同意这种方法是有意义的。@hakre实际上,我想再次确认您是否建议将
    feed
    从数组转换为对象(或拆分为单独的属性)。这种方法需要放松我们的验证,就像传统的RDBMS验证一样,通过列名进行保护(另外,我们可能需要处理不止一个ATOM和一个RSS提要)。否则,我看不出我们如何避免使用数组进行写倾斜。如果您保护传统的RDBMS,那么您使用的工具就不对了,Mongo不是RDBMS(也不是一个非常好的基于文档的数据库)。考虑这项工作的正确工具,否则你会遇到一系列问题,这些问题“没有特别地回答你需要的细节”。“HakRe让我们实际上把feed项目存储为文档,在一个单独的集合中,我认为这个工具很适合我们的需要。在这种情况下,一个站点只能有几个提要,如果我们正确阅读mongodb文档,那么嵌入是此类元数据的正确选项。唯一的问题是我们希望避免元数据的重复。看到这一点,我想:我们怎么没想到呢?这当然是一个有效的解决方案。这很容易是最好的答案,将在MongoDB环境下工作,一个分布式分片数据集。因为MongoDB的全部目的是在分片分布式设置中使用,我认为使用toku事务有点挫败了使用MongoDB的目的