Как обойти отсутствие транзакций в MongoDB?
Я знаю, что здесь есть похожие вопросы, но они либо говорят мне вернуться к обычным системам СУБД, если мне нужны транзакции, либо использовать атомарные операции или двухфазной фиксации. Второе решение кажется лучшим выбором. Третий я не хочу следовать, потому что кажется, что многие вещи могут пойти не так, и я не могу проверить его в каждом аспекте. У меня возникли трудности с рефакторингом моего проекта для выполнения атомарных операций. Я не знаю, происходит ли это от моя ограниченная точка зрения (до сих пор я работал только с базами данных SQL), или это действительно невозможно сделать.
мы хотели бы провести пилотный тест MongoDB в нашей компании. Мы выбрали относительно простой проект-SMS-шлюз. Это позволяет нашему программному обеспечению отправлять SMS-сообщения в сотовую сеть, а шлюз выполняет грязную работу: фактически общается с поставщиками через различные протоколы связи. Шлюз также управляет выставлением счетов за сообщения. Каждый клиент, который претендент на услугу должен купить несколько кредитов. Система автоматически уменьшает баланс пользователя при отправке сообщения и запрещает доступ, если баланс недостаточен. Кроме того, поскольку мы являемся клиентами сторонних поставщиков SMS, мы также можем иметь свои собственные балансы с ними. Мы также должны следить за ними.
Я начал думать о том, как я могу хранить необходимые данные с MongoDB, если я сократил некоторые сложности (внешний биллинг, отправка SMS в очереди). Приходят от в мире SQL я бы создал отдельную таблицу для пользователей, другую для SMS-сообщений и одну для хранения транзакций, касающихся баланса пользователей. Допустим, я создаю отдельные коллекции для всех тех, кто в MongoDB.
представьте себе задачу отправки SMS со следующими шагами в этой упрощенной системе:
проверьте, есть ли у пользователя достаточный баланс; запретить доступ, если нет достаточного кредита
отправить и сохранить сообщение в коллекции SMS с деталями и стоимостью (в живой системе сообщение будет иметь
statusатрибут и задача заберет его для доставки и установит цену SMS в соответствии с его текущим состоянием)уменьшить баланс пользователей на стоимость отправленного сообщения
зарегистрировать транзакцию в коллекции транзакций
Я придумал две идеи:
создайте единую коллекцию для пользователей и сохраните баланс в виде поля, связанных с пользователем транзакций и сообщений в качестве вложенных документов в документе пользователя. Потому что мы можем обновить документы атомарно, это фактически решает проблему транзакции. Недостатки: если пользователь отправляет много SMS-сообщений, размер документа может стать большим, и может быть достигнут предел документа 4 МБ. Может быть, я могу создавать исторические документы в таких сценариях, но я не думаю, что это было бы хорошей идеей. Также я не знаю, как быстро будет работать система, если я буду нажимать все больше и больше данных на один и тот же большой документ.
создайте одну коллекцию для пользователей и одну для транзакций. Существует два вида транзакций:покупка в кредит с положительным изменением баланса и сообщения с отрицательным изменением баланса. Транзакция может иметь вложенный документ; например, в сообщения детали SMS могут быть встроены в транзакцию. Недостатки: я не храню текущий баланс пользователя, поэтому я должен вычислять его каждый раз, когда пользователь пытается отправить сообщение, чтобы сказать, Может ли сообщение пройти или нет. Я боюсь этого расчет может замедлиться по мере роста числа хранимых транзакций.
Я немного запутался в том, какой метод выбрать. Есть ли другие решения? Я не мог найти никаких лучших практик в интернете о том, как обойти эти проблемы. Я думаю, что многие программисты, которые пытаются познакомиться с миром NoSQL, сталкиваются с аналогичными проблемами в начале.
10 ответов:
Регистрация этой по Tokutek. Они разрабатывают плагин для Mongo, который обещает не только транзакции, но и повышение производительности.
Жизнь Без Сделок
сопровождение сделок кислоты свойства, но хотя нет никаких транзакций в
MongoDB, У нас есть атомарные операции. Ну, атомарные операции означают, что когда вы работаете над одним документом, эта работа будет завершена до того, как кто-либо другой увидит документ. Они увидят все изменения, которые мы сделали, или ничего из них. И используя атомарные операции, вы часто можете выполнить то же самое, что и мы, используя операции в реляционной базе данных. И причина в том, что в реляционной базе данных, то нужно внести изменения в несколько таблиц. Обычно таблицы, которые должны быть объединены, и поэтому мы хотим сделать это все сразу. И чтобы сделать это, так как есть несколько таблиц, мы должны будем начать транзакцию и сделать все эти обновления, а затем завершить транзакцию. Но сMongoDB, мы собираемся внедрить данные, так как мы собираемся pre-join это в документах и они эти богатые документы что есть иерархия. Мы часто можем сделать то же самое. Например, в Примере блога, если мы хотим убедиться, что мы обновили сообщение в блоге атомарно, мы можем сделать это, потому что мы можем обновить весь пост в блоге сразу. Где, как если бы это была куча реляционных таблиц, нам, вероятно, придется открыть транзакцию, чтобы мы могли обновить коллекцию post и коллекцию комментариев.Итак, каковы наши подходы, которые мы можем предпринять в
MongoDBчтобы преодолеть недостаток сделки?
- реструктуризация - реструктурируйте код, чтобы мы работали в рамках одного документа и использовали преимущества атомарных операций, которые мы предлагаем в этом документе. И если мы это делаем, то обычно все готово.
- реализовать в программном обеспечении - мы можем реализовать блокировку в программном обеспечении, создав критический раздел. Мы можем построить тест, проверить и установить, используя найти и изменить. Мы можем построить семафоры, если это необходимо. И в каком-то смысле именно так устроен большой мир. Если мы подумаем об этом, если один банк должен перевести деньги в другой банк, они не живут в одной и той же реляционной системе. И каждый из них часто имеет свои собственные реляционные базы данных. И они должны быть в состоянии координировать эту операцию, даже если мы не можем начать транзакцию и закончить транзакцию через эти системы баз данных, только в одной системе в одном банке. Так что есть, конечно, способы в программном обеспечении, чтобы обойти эту проблему.
- терпеть - окончательный подход, который часто работает в современных веб-приложениях и других приложениях, которые принимают огромное количество данных, - это просто терпеть немного непоследовательности. Например, если мы говорим о ленте друзей в Facebook, не имеет значения, если все видят ваше обновление стены одновременно. Если хорошо, если один человек отстает на несколько ударов на несколько секунд, и они догоняют. Это часто не критично во многих системных проектах, что все будьте абсолютно последовательны и чтобы все имели совершенно последовательное и одинаковое представление о базе данных. Таким образом, мы могли бы просто терпеть немного непоследовательности, которая является несколько временной.
Update,findAndModify,$addToSet(в обновления) &$push(в рамках обновления) операции работают атомарно в пределах одного документа.
начиная с 4.0, MongoDB будет иметь транзакции с несколькими документами ACID. Планируется включить в набор реплик развертывание, а затем сегментируется кластера. Транзакции в MongoDB будут чувствовать себя так же, как разработчики транзакций знакомы с реляционными базами данных - они будут многооперационными, с аналогичной семантикой и синтаксисом (например,
start_transactionиcommit_transaction). Важно отметить, что изменения в MongoDB, которые включают транзакции, не влияют на производительность для рабочих нагрузок, которые не требуют их.для получения более подробной информации см. здесь.
довести его до точки: если транзакционная целостность-это должны тогда не используйте MongoDB, а используйте только компоненты в системе, поддерживающей транзакции. Чрезвычайно трудно построить что-то на верхней части компонента для того чтобы обеспечить кислотно-подобный функционал не кислота совместимых компонентов. В зависимости от индивидуальных особенностей использования может иметь смысл каким-то образом разделить действия на транзакционные и нетранзакционные...
в чем проблема с этим? MongoDB может выполнять атомарные обновления только для одного документа. В предыдущем потоке может произойти какая-то ошибка, и сообщение будет сохранено в базе данных, но баланс пользователя не будет уменьшен и/или транзакция не будет зарегистрирована.это не проблема. Ошибка, которую вы упомянули, является либо логической (ошибка), либо ошибкой ввода-вывода (сеть, сбой диска). Такая ошибка может оставить оба транзакционные и транзакционные магазины в несогласованном состоянии. Например, если он уже отправил SMS, но при сохранении сообщения произошла ошибка - он не может откатить отправку SMS, что означает, что он не будет зарегистрирован, баланс пользователя не будет уменьшен и т. д.
реальная проблема здесь заключается в том, что пользователь может воспользоваться состоянием гонки и отправить больше сообщений, чем позволяет его баланс. Это также относится к СУБД, если вы не отправляете SMS внутри транзакции с блокировкой поля баланса (что было бы большое узкое место). В качестве возможного решения для MongoDB будет использовать
findAndModifyво-первых, чтобы уменьшить баланс и проверить его, если он отрицательный запретить отправку и возврат суммы (атомный инкремент). Если положительный результат, продолжить отправку и в случае неудачи вернуть сумму. Коллекция истории баланса также может быть сохранена, чтобы помочь исправить / проверить поле баланса.
проект прост, но вы должны поддерживать транзакции для оплаты, что делает все это сложным. Так, например, сложная портальная система с сотнями коллекций (форум, чат, объявления и др.)...) в некотором отношении проще, потому что, если вы потеряете форум или запись в чате, никто не заботится. Если вы, с другой стороны, потеряете платежную транзакцию, это серьезная проблема.
Итак, если вы действительно хотите пилотный проект для MongoDB, выберите тот, который прост в это уважения.
транзакции отсутствуют в MongoDB по уважительным причинам. Это одна из тех вещей, которые делают MongoDB быстрее.
в вашем случае, если транзакция является обязательной,монго кажется не очень подходящим.
может быть RDMBS + MongoDB, но это добавит сложности и затруднит управление и поддержку приложения.
Это, вероятно, лучший блог, который я нашел в отношении реализации транзакции, такой как функция для mongodb .!
флаг синхронизации: лучше всего для простого копирования данных из основного документа
очередь заданий: очень общего назначения, решает 95% случаев. Большинство систем должны иметь по крайней мере одну очередь заданий вокруг в любом случае!
двухфазная фиксация: этот метод гарантирует, что каждый объект всегда имеет всю информацию, необходимую для достижения согласованного состояния
Log Согласование: самый надежный метод, идеально подходит для финансовых систем
управление версиями: обеспечивает изоляцию и поддерживает сложные структуры
прочитайте это для получения дополнительной информации:https://dzone.com/articles/how-implement-robust-and
это поздно, но думаю, что это поможет в будущем. Я использую Рэдис к очереди чтобы решить эту проблему.
требования:
Изображение ниже показывает, что 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 for кислоты цель и масштабируемость.
Редис документ говорить об этом по сделке:все команды в транзакции сериализуются и выполняются последовательно. Это никогда не может случиться, что запрос, выданный другим клиент обслуживается в середине выполнения 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(); }

Comments