Как обойти отсутствие транзакций в MongoDB?

Я знаю, что здесь есть похожие вопросы, но они либо говорят мне вернуться к регулярным системам РСУБД, если мне нужны транзакции или использовать атомные операции или двухфазную фиксацию . Второй вариант кажется лучшим выбором. Третий я не хочу следовать, потому что кажется, что многие вещи могут пойти не так, и я не могу проверить это во всех аспектах. Мне сложно реорганизовать мой проект для выполнения атомных операций. Я не знаю, связано ли это с моей ограниченной точки зрения (я только работал с базами данных SQL до сих пор), или это действительно невозможно.

Мы хотели бы пилотировать тест MongoDB в нашей компании. Мы выбрали относительно простой проект – шлюз SMS. Это позволяет нашему программному обеспечению отправлять SMS-сообщения в сотовую сеть, а шлюз выполняет грязную работу: фактически общается с поставщиками через различные протоколы связи. Шлюз также управляет выставлением счетов. Каждый клиент, который подает заявку на услугу, должен купить несколько кредитов. Система автоматически уменьшает баланс пользователя при отправке сообщения и отказывает в доступе, если баланс недостаточен. Кроме того, поскольку мы являемся клиентами сторонних провайдеров SMS, у нас также могут быть свои собственные балансы. Мы должны следить за ними.

Я начал думать о том, как я могу хранить требуемые данные с помощью MongoDB, если я скрою некоторую сложность (внешняя биллинг, отправка посылок в очереди). Исходя из мира SQL, я бы создал отдельную таблицу для пользователей, другую для SMS-сообщений и одну для хранения транзакций относительно баланса пользователей. Предположим, я создаю отдельные коллекции для всех в MongoDB.

Представьте задачу отправки SMS со следующими шагами в этой упрощенной системе:

  1. проверьте, имеет ли пользователь достаточный баланс; запретить доступ, если недостаточно кредитов

  2. отправьте и сохраните сообщение в compilationе SMS с подробной информацией и стоимостью (в живой системе сообщение будет иметь атрибут status и задача будет забрать его для доставки и установить цену SMS в соответствии с его текущим состоянием)

  3. уменьшить баланс пользователя на стоимость отправленного сообщения

  4. регистрировать транзакцию в коллекции транзакций

Теперь в чем проблема? MongoDB может выполнять атомарные обновления только на одном документе. В предыдущем streamе может случиться, что в базу данных заходит какая-то ошибка, и сообщение сохраняется в базе данных, но баланс пользователя не обновляется и / или транзакция не регистрируется.

Я придумал две идеи:

  • Создайте единую коллекцию для пользователей и сохраните баланс как поле, связанные с пользователем транзакции и сообщения в качестве поддокументов в документе пользователя. Поскольку мы можем обновлять документы атомарно, это фактически решает проблему транзакции. Недостатки: если пользователь отправляет много SMS-сообщений, размер документа может стать большим и может быть достигнут предел документа 4 МБ. Возможно, я могу создавать документы истории в таких сценариях, но я не думаю, что это была бы хорошая идея. Кроме того, я не знаю, насколько бы быстрой была система, если я вытащу все больше и больше данных в один и тот же большой документ.

  • Создайте одну коллекцию для пользователей и одну для транзакций. Могут быть два вида сделок: покупка кредита с положительным изменением баланса и сообщения, отправленные с отрицательным сальдо. Транзакция может иметь поддокумент; например, в отправляемых сообщениях детали SMS могут быть встроены в транзакцию. Недостатки: я не сохраняю текущий баланс пользователя, поэтому я должен рассчитать его каждый раз, когда пользователь пытается отправить сообщение, чтобы сообщить, может ли сообщение пройти или нет. Я боюсь, что этот расчет может стать медленным, так как количество хранимых транзакций растет.

Я немного смущен тем, какой метод выбрать. Существуют ли другие решения? Я не мог найти ни одного передового опыта в Интернете о том, как обойти эти проблемы. Думаю, многие программисты, которые пытаются познакомиться с миром NoSQL, сталкиваются с аналогичными проблемами в начале.

Проверьте это , Tokutek. Они разрабатывают плагин для Mongo, который обещает не только транзакции, но и повышение производительности.

Жизнь без транзакций

Транзакции поддерживают свойства ACID, но, хотя транзакций в MongoDB , у нас есть атомарные операции. Ну, атомные операции означают, что когда вы работаете над одним документом, эта работа будет завершена до того, как кто-либо еще увидит документ. Они увидят все сделанные нами изменения или ни одно из них. И используя атомные операции, вы часто можете выполнить ту же самую вещь, которую мы выполнили бы с помощью транзакций в реляционной базе данных. И причина в том, что в реляционной базе данных нам нужно внести изменения в несколько таблиц. Обычно таблицы должны быть объединены, и поэтому мы хотим сделать это все сразу. И для этого, поскольку существует несколько таблиц, нам нужно будет начать транзакцию и выполнить все эти обновления, а затем завершить транзакцию. Но с MongoDB мы собираемся внедрить данные, поскольку мы собираемся предварительно присоединиться к ним в документах, и это эти богатые документы, которые имеют иерархию. Мы можем часто выполнять одно и то же. Например, в примере блога, если мы хотим убедиться, что мы обновили сообщение в блоге атомарно, мы можем это сделать, потому что мы можем сразу обновить весь блог. Если бы это была куча реляционных таблиц, нам, вероятно, пришлось бы открыть транзакцию, чтобы мы могли обновлять коллекцию записей и комментариев.

Итак, каковы наши подходы, которые мы можем предпринять в MongoDB для преодоления отсутствия транзакций?

  • реструктурировать – реструктурировать код, чтобы мы работали в рамках одного документа и использовали атомные операции, которые мы предлагаем в этом документе. И если мы это сделаем, тогда, как правило, мы все настроены.
  • реализовать в программном обеспечении – мы можем реализовать блокировку в программном обеспечении, создав критический раздел. Мы можем построить тест, тест и установить с помощью поиска и изменения. Мы можем строить семафоры, если это необходимо. И в некотором роде, так устроен более крупный мир. Если мы подумаем об этом, если одному банку необходимо перевести деньги в другой банк, они не будут жить в одной и той же реляционной системе. И у каждого из них есть свои собственные реляционные базы данных. И они должны иметь возможность координировать эту операцию, даже если мы не можем начинать транзакцию и завершать транзакцию в этих системах баз данных только внутри одной системы внутри одного банка. Таким образом, в программном обеспечении есть определенные способы обойти проблему.
  • терпим – окончательный подход, который часто работает в современных веб-приложениях и других приложениях, которые берут огромное количество данных, – это просто терпеть небольшую несогласованность. Например, если мы говорим о фиде друзей в Facebook, неважно, будут ли все одновременно видеть обновление вашей стены. Если okey, если один человек несколько бьет позади в течение нескольких секунд, и они догоняют. Часто во многих конструкциях системы не критично, что все должно быть идеально согласованным и что каждый имеет абсолютно последовательный и одинаковый вид базы данных. Поэтому мы могли бы просто терпеть немного несоответствия, которое несколько временное.

Update , findAndModify , $addToSet (в рамках обновления) и $push (в рамках обновления) работают атомарно внутри одного документа.

Начиная с 4.0, MongoDB будет иметь транзакции ACID с несколькими документами. План состоит в том, чтобы сначала включить развертывание набора реплик, за которым следуют осколочные кластеры. Транзакции в MongoDB будут ощущаться так же, как разработчики транзакций знакомы с реляционными базами данных – они будут многозадачными, с подобной семантикой и синтаксисом (например, start_transaction и commit_transaction ). Важно отметить, что изменения в MongoDB, которые позволяют транзакции, не влияют на производительность для рабочих нагрузок, которые их не требуют.

Подробнее см. Здесь .

Приведите его к сути: если целостность транзакций является обязательной, тогда не используйте MongoDB, а используйте только компоненты в системе, поддерживающей транзакции. Чрезвычайно сложно создать что-то поверх компонента, чтобы обеспечить ACID-подобную функциональность для компонентов, не совместимых с ACID. В зависимости от индивидуальных условий использования может иметь смысл отделить действия в транзакционных и не транзакционных действиях каким-то образом …

Теперь в чем проблема? MongoDB может выполнять атомарные обновления только на одном документе. В предыдущем streamе может случиться, что в базу данных заходит какая-то ошибка, и сообщение сохраняется в базе данных, но баланс пользователя не уменьшается и / или транзакция не регистрируется.

Это не проблема. Вы упомянули ошибку либо логическую ошибку (ошибка), либо ошибку ввода-вывода (сеть, сбой диска). Такая ошибка может оставлять как транзакционные, так и транзакционные хранилища в несогласованном состоянии. Например, если он уже отправил SMS, но при сохранении ошибки сообщения произошел – он не может отменить отправку SMS, что означает, что он не будет зарегистрирован, баланс пользователя не будет уменьшен и т. Д.

Настоящая проблема заключается в том, что пользователь может воспользоваться условиями гонки и отправлять больше сообщений, чем позволяет его баланс. Это также относится к РСУБД, если только вы не отправляете SMS внутри транзакции с блокировкой поля баланса (что было бы большим препятствием). В качестве возможного решения для MongoDB сначала будет использовать findAndModify чтобы уменьшить баланс и проверить его, если это отрицательное отклонение отправки и возврата суммы (атомный прирост). Если положительный, продолжите отправку и в случае, если он не сможет вернуть сумму. Также можно сохранить коллекцию истории баланса, чтобы помочь исправить / проверить поле баланса.

Проект прост, но вы должны поддерживать транзакции для оплаты, что усложняет задачу. Так, например, сложная система портала с сотнями коллекций (форум, чат, реклама и т. Д.) В некотором отношении проще, потому что, если вы теряете форум или запись в чате, никто действительно не заботится. Если вы, с другой стороны, потеряете транзакцию платежа, это серьезная проблема.

Итак, если вам действительно нужен пилотный проект для MongoDB, выберите тот, который прост в этом отношении.

Справедливые причины сделок в MongoDB отсутствуют. Это одна из тех вещей, которые делают MongoDB быстрее.

В вашем случае, если транзакция является обязательной, mongo кажется не очень подходящей.

Может быть RDMBS + MongoDB, но это добавит сложности и затруднит управление и поддержку приложения.

Вероятно, это лучший блог, который я нашел для реализации транзакции, такой как функция для mongodb.!

Флаг синхронизации: лучше всего просто копировать данные из основного документа

Очередь работы: очень общая цель, решает 95% случаев. В любом случае в большинстве систем должно быть как минимум одна очередь заданий!

Двухфазная фиксация: этот метод гарантирует, что каждый объект всегда имеет всю информацию, необходимую для достижения согласованного состояния

Log Reconsciliation: самый надежный метод, идеально подходящий для финансовых систем

Версии: обеспечивает изоляцию и поддерживает сложные структуры

Читайте это для получения дополнительной информации: https://dzone.com/articles/how-implement-robust-and

Это поздно, но подумайте, что это поможет в будущем. Я использую Redis для создания очереди для решения этой проблемы.

  • Требование:
    На рисунке ниже показано, что 2 действия необходимо выполнить одновременно, но фаза 2 и фаза 3 действия 1 должны завершиться до начала фазы 2 действия 2 или наоборот (фаза может быть запросом REST api, запросом базы данных или исполнением кода JavaScript …). введите описание изображения здесь

  • Как очередь помогает вам
    Очередь убедитесь, что каждый код блока между lock() и release() во многих функциях не будет запускаться в одно и то же время, чтобы их изолировать.

     function action1() { phase1(); queue.lock("action_domain"); phase2(); phase3(); queue.release("action_domain"); } function action2() { phase1(); queue.lock("action_domain"); phase2(); queue.release("action_domain"); } 
  • Как создать очередь
    Я сосредоточусь только на том, как избежать компромиссной ситуации при создании очереди на веб-сайте. Если вы не знаете основную идею очереди, приходите сюда .
    Приведенный ниже код показывает только концепцию, которую нужно реализовать правильно.

     function lock() { if(isRunning()) { addIsolateCodeToQueue(); //use callback, delegate, function pointer... depend on your language } else { setStateToRunning(); pickOneAndExecute(); } } function release() { setStateToRelease(); pickOneAndExecute(); } 

Но вам нужно isRunning() setStateToRelease() setStateToRunning() изолировать его самостоятельно, иначе вы снова столкнетесь с состоянием гонки. Для этого я выбираю Redis для целей ACID и масштабируемости.
В документе Redis говорится о транзакции:

Все команды в транзакции сериализуются и выполняются последовательно. Никогда не может случиться, что запрос, выданный другим клиентом, обслуживается в середине выполнения транзакции Redis. Это гарантирует, что команды выполняются как одна изолированная операция.

P / S:
Я использую Redis, потому что мой сервис уже использует его, вы можете использовать любой другой способ поддержки изоляции для этого.
action_domain в моем коде выше, если вам нужно только действие 1 вызов пользователем. Действие блока 2 пользователя A, не блокируйте другого пользователя. Идея ставится уникальный ключ для блокировки каждого пользователя.

Теперь транзакции доступны в MongoDB 4.0. Образец здесь

 // Runs the txnFunc and retries if TransientTransactionError encountered function runTransactionWithRetry(txnFunc, session) { while (true) { try { txnFunc(session); // performs transaction break; } catch (error) { // If transient error, retry the whole transaction if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError") ) { print("TransientTransactionError, retrying transaction ..."); continue; } else { throw error; } } } } // Retries commit if UnknownTransactionCommitResult encountered function commitWithRetry(session) { while (true) { try { session.commitTransaction(); // Uses write concern set at transaction start. print("Transaction committed."); break; } catch (error) { // Can retry commit if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) { print("UnknownTransactionCommitResult, retrying commit operation ..."); continue; } else { print("Error during commit ..."); throw error; } } } } // Updates two collections in a transactions function updateEmployeeInfo(session) { employeesCollection = session.getDatabase("hr").employees; eventsCollection = session.getDatabase("reporting").events; session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } ); try{ employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } ); eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } ); } catch (error) { print("Caught exception during transaction, aborting."); session.abortTransaction(); throw error; } commitWithRetry(session); } // Start a session. session = db.getMongo().startSession( { mode: "primary" } ); try{ runTransactionWithRetry(updateEmployeeInfo, session); } catch (error) { // Do something with error } finally { session.endSession(); } 
  • Дублирование fragmentов на транзакции fragmentов
  • TransactionScope автоматически переходит на MSDTC на некоторых машинах?
  • EJB-транзакции в локальных методах-вызовах
  • Как интегрировать весну с сеансом гибернации и управлением транзакциями?
  • Каков уровень изоляции транзакций по умолчанию для SQL Server с ADO.NET?
  • javax.transaction.Transactional vs org.springframework.transaction.annotation.Transactional
  • Что произойдет, если вы не совершаете транзакцию с базой данных (скажем, SQL Server)?
  • Django: как я могу защитить от одновременной модификации записей в базе данных
  • Как TransactionScope откатывает транзакции?
  • Oracle: как узнать, ожидает ли транзакция?
  • Фиксация «Тайм-аут блокировки ожидания превышен; попробуйте перезапустить транзакцию «за« застрявшую »таблицу Mysql?
  • Давайте будем гением компьютера.