Как синхронизировать два приложения, работающие на двух разных серверах, через MongoDB - PullRequest
0 голосов
/ 23 мая 2018

Я занимаюсь разработкой веб-приложения на golang и использую один экземпляр MongoDB в качестве хранилища данных.У меня есть код, который должен быть выполнен исключительно.Поскольку мое веб-приложение работает на двух разных серверах, я не могу использовать для этого инструменты синхронизации golang.

Идея состоит в том, чтобы использовать MongoDB для этого путем блокировки документа, но я не знаю, возможно ли это, и если да, то как это сделать?

Ответы [ 2 ]

0 голосов
/ 23 мая 2018

Для отдельных документов операции обновления в Mongo являются атомарными .Ваши веб-приложения будут получать согласованное представление документа, поскольку запрос «записи» обновит сразу все поля или вообще не обновит их.Примеры по приведенной выше ссылке.

Если экземпляры веб-приложения обновляют несколько документов в одном запросе (если вы пакетируете их с помощью updateMany), атомарные операции недоступны.Вы можете использовать встроенные документы для адресации (ссылка выше) или документ, сбор или блокировка базы данных , которая обеспечивает различные блокировки чтения-записи.

Приведенные выше ссылки имеют право на бизнес, но дляполное представление, вот страница документации mongo .

Если вы могли бы более детально описать ваш поток, сообщество может дать вам более конкретный ответ.

0 голосов
/ 23 мая 2018

Обратите внимание: использование Redis было бы лучшим и более эффективным выбором для распределенной блокировки.

Но если вы все еще хотите использовать MongoDB для этого, читайте дальше.

Некоторые примечания к приведенным ниже решениям:

  • Все приведенные ниже решения безопасны и работают, даже если у вас несколько серверов MongoDB (общий кластер), поскольку ни одно из приведенных ниже решений не полагается на простыечитает;и все записи (например, insert или update) отправляются в мастер-экземпляр.

  • Если программа не может получить блокировку, она может решить немного поспать (например, 1второе), затем повторите попытку получения блокировки.Должно быть максимальное число повторов до отказа.


Использование существования документа в качестве блокировки

Самый простой способ - полагать, что MongoDB не позволяетСуществуют 2 документа с одинаковым идентификатором (в одной коллекции).

Таким образом, чтобы получить блокировку, просто вставьте документ в назначенную коллекцию (например, 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")

Плюсы: Простейшийрешение.

Минусы: Вы можете указать одно и то же время ожидания для всех блокировок, его сложнее изменить позже (необходимо удалить и заново создать индекс).

Обратите внимание, что этоПоследнее утверждение не совсем верно, потому что вы не обязаны устанавливать текущее время в поле lockedAt.Например, если вы установите временную метку, указывающую 5 секунд в прошлом, блокировка автоматически истечет через 25 секунд.Если вы установите его на 5 секунд на будущее, срок действия блокировки истечет через 35 секунд.

Также обратите внимание, что если программа получает блокировку, и без каких-либо проблем ей нужно удерживать ее дольше 30 секунд, онаможет быть сделано путем обновления поля lockedAt документа блокировки.Например, через 20 секунд, если у программы нет проблем, но требуется больше времени, чтобы завершить свою работу, удерживая блокировку, она может обновить поле lockedAt до текущего времени, предотвращая его автоматическое удаление (и, таким образом, давая зеленый светдругие программы, ожидающие этой блокировки).

Использование предварительно созданных документов блокировки и update()

Другим решением может быть создание коллекции с предварительно созданными документами блокировки.Блокировки могут иметь идентификатор (_id) и состояние, указывающее, заблокирован он или нет (locked).

Предварительно создать блокировку:

db.locks.insert({_id:"l1", locked:false})

Чтобы получитьБлокировка, используйте метод Collection.Update(), где в селекторе вы должны фильтровать по идентификатору и заблокированному состоянию, где состояние должно быть разблокировано.И значение обновления должно быть $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) пытаются получить блокировку, только один из них будет успешным, потому что селектор для остальных вернет 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},
})

Плюсы: Вы можете использовать разные таймауты для разных замков или даже для одних и тех же замков в разных местах (хотя это будетплохая практика).

Минусы: Немного сложнее.

Обратите внимание, что для «продления срока службы» замка можно использовать ту же технику, которая описанавыше, то есть, если срок действия блокировки приближается, и программе требуется больше времени, он может обновить поле lockedAt документа блокировки.

...