我正在用golang开发一个web应用程序,并使用单个MongoDB实例作为数据存储。我有应该专门执行的代码。因为我的web应用程序运行在两台不同的服务器上,所以我不能使用golang同步工具。
的想法是通过锁定文档来使用MongoDB,但是我不知道这是否可能,如果是的话,如何做到呢?
发布于 2018-05-23 12:27:53
注意:使用Redis将是分布式锁定的更好和更有效的选择。
但是,如果您仍然希望使用MongoDB进行此操作,请继续阅读。
以下是解决办法的一些说明:
insert或update)都会转到主实例。使用文档的存在作为锁
最简单的方法是依赖MongoDB,不允许两个ID相同的文档存在(在同一个集合中)。
因此,要获取锁,只需使用锁ID将文档插入指定集合(例如locks)。如果插入成功,则成功获得锁。如果插入失败,则不会。要释放锁,只需删除(删除)文档。
需要注意的是:您必须释放锁,因为如果没有这样做,所有试图获取该锁的代码都不会成功。因此,应该使用延迟函数(defer)来释放锁。不幸的是,这不能确保在发生通信错误(网络故障)时发布。
为了保证锁的释放,您可以创建一个指定文件过期的索引,这样,如果Go应用程序在保持锁时出现任何问题,锁将在一段时间后自动删除。
示例:
锁文档不先插入。但是需要一个索引:
db.locks.createIndex({lockedAt: 1}, {expireAfterSeconds: 30})获取锁:
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
}释放锁:
err := c.RemoveId("l1")Pros:最简单的解决方案。
Cons:您只能为所有锁指定相同的超时,以后更难更改它(必须删除并重新创建索引)。
请注意,最后一条语句并不完全正确,因为您没有被迫将当前时间设置为lockedAt字段。如果你在过去设置了5秒的时间戳,锁将在25秒后自动过期。如果您将其设置为未来的5秒,则该锁将在35秒后过期。
还请注意,如果一个goroutine获得了一个锁,并且没有任何问题,它需要保持它超过30秒,它可以通过更新锁文档的lockedAt字段来完成。例如,20秒后,如果goroutine没有遇到任何问题,但是需要更多的时间来完成锁的工作,那么它可能会将lockedAt字段更新到当前的时间,从而阻止它被自动删除(从而为等待该锁的其他猩猩提供了绿灯)。
使用预先创建的锁文档和update()
另一种解决方案可能是拥有一个带有预先创建的锁文档的集合。锁可以有一个ID (_id),并且有一个状态来指示它是否被锁定(locked)。
先创建一个锁:
db.locks.insert({_id:"l1", locked:false})若要获得锁,请使用Collection.Update()方法,在选择器中,必须按ID和锁定状态进行筛选,必须解锁其中的状态。更新值应该是一个$set操作,将锁定状态设置为true。
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。
一旦你用你的东西握住锁,你就必须释放锁:
err := c.UpdateId("l1", bson.M{
"$set": bson.M{"locked": false},
})若要保证锁释放,您可以在锁定文档中包含一个时间戳。当试图获取锁时,选择器还应该接受锁定但超过给定超时时间(例如30秒)的锁。在这种情况下,更新还应该设置锁定的时间戳。
使用超时确保锁释放的示例:
锁定文件:
db.locks.insert({_id:"l1", locked:false})获取锁:
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
}释放锁:
err := c.UpdateId("l1", bson.M{
"$set": bson.M{ "locked": false},
})Pros:您可以对不同的锁使用不同的超时,甚至对不同位置的相同锁使用不同的超时(尽管这可能是不好的做法)。
Cons:稍微复杂一些。
请注意,要“延长锁的生存期”,可以使用上面描述的相同技术,即,如果锁过期接近,而goroutine需要更多时间,则可能更新锁文档的lockedAt字段。
发布于 2018-05-23 12:31:32
对于单个文档,请在Mongo 是原子的中更新操作。您的web应用程序都将收到文档的一致视图,因为“写”请求将同时更新所有字段,或者根本不更新所有字段。以上链接中的示例。
如果web应用实例在一个查询中更新多个文档(如果使用updateMany对它们进行批处理),则原子操作不可用。您可以使用嵌入式文档来解决(上面的链接)或提供不同读写锁的文档、集合或数据库锁定。
上面的链接可以直接指向业务,但是要全面了解这是mongo文档页面。
如果你能进一步详细说明你的流程,社区可能会给你一个更具体的答案。
https://stackoverflow.com/questions/50488013
复制相似问题