首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何通过MongoDB同步在两台不同服务器上运行的两个应用程序

问如何通过MongoDB同步在两台不同服务器上运行的两个应用程序
EN

Stack Overflow用户
提问于 2018-05-23 12:07:46
回答 2查看 1.5K关注 0票数 4

我正在用golang开发一个web应用程序,并使用单个MongoDB实例作为数据存储。我有应该专门执行的代码。因为我的web应用程序运行在两台不同的服务器上,所以我不能使用golang同步工具。

的想法是通过锁定文档来使用MongoDB,但是我不知道这是否可能,如果是的话,如何做到呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-05-23 12:27:53

注意:使用Redis将是分布式锁定的更好和更有效的选择。

但是,如果您仍然希望使用MongoDB进行此操作,请继续阅读。

以下是解决办法的一些说明:

  • 即使您有多个MongoDB服务器(共享集群),下面的所有解决方案都是安全和工作的,因为下面的解决方案都不依赖简单读取;而且所有的写入(例如insert或update)都会转到主实例。
  • 如果一个大猩猩没有获得一个锁,它可能决定睡一会儿(例如1秒),然后重试获得锁。在放弃之前应该有一个最大重试数。

使用文档的存在作为锁

最简单的方法是依赖MongoDB,不允许两个ID相同的文档存在(在同一个集合中)。

因此,要获取锁,只需使用锁ID将文档插入指定集合(例如locks)。如果插入成功,则成功获得锁。如果插入失败,则不会。要释放锁,只需删除(删除)文档。

需要注意的是:您必须释放锁,因为如果没有这样做,所有试图获取该锁的代码都不会成功。因此,应该使用延迟函数(defer)来释放锁。不幸的是,这不能确保在发生通信错误(网络故障)时发布。

为了保证锁的释放,您可以创建一个指定文件过期的索引,这样,如果Go应用程序在保持锁时出现任何问题,锁将在一段时间后自动删除。

示例:

锁文档不先插入。但是需要一个索引:

代码语言:javascript
复制
db.locks.createIndex({lockedAt: 1}, {expireAfterSeconds: 30})

获取锁:

代码语言:javascript
复制
sess := ... // Obtain a MongoDB session
c := sess.DB("").C("locks")

err := c.Insert(bson.M{
    "_id":      "l1",
    "lockedAt": time.Now(),
})
if err == nil {
    // LOCK OBTAINED! DO YOUR STUFF
}

释放锁:

代码语言:javascript
复制
err := c.RemoveId("l1")

Pros:最简单的解决方案。

Cons:您只能为所有锁指定相同的超时,以后更难更改它(必须删除并重新创建索引)。

请注意,最后一条语句并不完全正确,因为您没有被迫将当前时间设置为lockedAt字段。如果你在过去设置了5秒的时间戳,锁将在25秒后自动过期。如果您将其设置为未来的5秒,则该锁将在35秒后过期。

还请注意,如果一个goroutine获得了一个锁,并且没有任何问题,它需要保持它超过30秒,它可以通过更新锁文档的lockedAt字段来完成。例如,20秒后,如果goroutine没有遇到任何问题,但是需要更多的时间来完成锁的工作,那么它可能会将lockedAt字段更新到当前的时间,从而阻止它被自动删除(从而为等待该锁的其他猩猩提供了绿灯)。

使用预先创建的锁文档和update()

另一种解决方案可能是拥有一个带有预先创建的锁文档的集合。锁可以有一个ID (_id),并且有一个状态来指示它是否被锁定(locked)。

先创建一个锁:

代码语言:javascript
复制
db.locks.insert({_id:"l1", locked:false})

若要获得锁,请使用Collection.Update()方法,在选择器中,必须按ID和锁定状态进行筛选,必须解锁其中的状态。更新值应该是一个$set操作,将锁定状态设置为true。

代码语言:javascript
复制
err := c.Update(bson.M{
    "_id":    "l1",
    "locked": false,
}, bson.M{
    "$set": bson.M{"locked": true},
})
if err == nil {
    // LOCK OBTAINED! DO YOUR STUFF
}

这是怎么回事?如果多个Go实例(甚至同一Go应用程序中的多个goroutines )试图获得锁,则只有一个成功,因为其余的选择器将返回mgo.ErrNotFound,因为占上风的选择器将locked字段设置为true。

一旦你用你的东西握住锁,你就必须释放锁:

代码语言:javascript
复制
err := c.UpdateId("l1", bson.M{
    "$set": bson.M{"locked": false},
})

若要保证锁释放,您可以在锁定文档中包含一个时间戳。当试图获取锁时,选择器还应该接受锁定但超过给定超时时间(例如30秒)的锁。在这种情况下,更新还应该设置锁定的时间戳。

使用超时确保锁释放的示例:

锁定文件:

代码语言:javascript
复制
db.locks.insert({_id:"l1", locked:false})

获取锁:

代码语言:javascript
复制
err := c.Update(bson.M{
    "_id": "l1",
    "$or": []interface{}{
        bson.M{"locked": false},
        bson.M{"lockedAt": bson.M{"$lt": time.Now().Add(-30 * time.Second)}},
    },
}, bson.M{
    "$set": bson.M{
        "locked":   true,
        "lockedAt": time.Now(),
    },
})
if err == nil {
    // LOCK OBTAINED! DO YOUR STUFF
}

释放锁:

代码语言:javascript
复制
err := c.UpdateId("l1", bson.M{
    "$set": bson.M{ "locked": false},
})

Pros:您可以对不同的锁使用不同的超时,甚至对不同位置的相同锁使用不同的超时(尽管这可能是不好的做法)。

Cons:稍微复杂一些。

请注意,要“延长锁的生存期”,可以使用上面描述的相同技术,即,如果锁过期接近,而goroutine需要更多时间,则可能更新锁文档的lockedAt字段。

票数 5
EN

Stack Overflow用户

发布于 2018-05-23 12:31:32

对于单个文档,请在Mongo 是原子的中更新操作。您的web应用程序都将收到文档的一致视图,因为“写”请求将同时更新所有字段,或者根本不更新所有字段。以上链接中的示例。

如果web应用实例在一个查询中更新多个文档(如果使用updateMany对它们进行批处理),则原子操作不可用。您可以使用嵌入式文档来解决(上面的链接)或提供不同读写锁的文档、集合或数据库锁定。

上面的链接可以直接指向业务,但是要全面了解这是mongo文档页面。

如果你能进一步详细说明你的流程,社区可能会给你一个更具体的答案。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50488013

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档